blob: 544f13692e1dee5f9499bf4295edc597e95fbe48 [file] [log] [blame]
// Copyright 2016 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <stdlib.h>
// Must be included before anything else.
#include "src/tools/singlejar/port.h"
#include "src/main/cpp/util/file.h"
#include "src/main/cpp/util/port.h"
#include "src/main/cpp/util/strings.h"
#include "src/tools/singlejar/input_jar.h"
#include "src/tools/singlejar/options.h"
#include "src/tools/singlejar/output_jar.h"
#include "src/tools/singlejar/test_util.h"
#include "googletest/include/gtest/gtest.h"
#if !defined(JAR_TOOL_PATH)
#error "The path to jar tool has to be defined via -DJAR_TOOL_PATH="
#endif
#ifdef _WIN32
#define unlink _unlink
#define CMD_SEPARATOR "&"
#else
#define CMD_SEPARATOR ";"
#endif
namespace {
using bazel::tools::cpp::runfiles::Runfiles;
using singlejar_test_util::CreateTextFile;
using singlejar_test_util::GetEntryContents;
using singlejar_test_util::OutputFilePath;
using singlejar_test_util::RunCommand;
using singlejar_test_util::VerifyZip;
using std::string;
#if !defined(DATA_DIR_TOP)
#define DATA_DIR_TOP
#endif
const char kPathLibData1[] =
"io_bazel/src/tools/singlejar/libdata1.jar";
const char kPathLibData2[] =
"io_bazel/src/tools/singlejar/libdata2.jar";
static bool HasSubstr(const string &s, const string &what) {
return string::npos != s.find(what);
}
static bool EndsWith(const string &s, const string &what) {
return what.size() <= s.size() && s.substr(s.size() - what.size()) == what;
}
// A subclass of the OutputJar which concatenates the contents of each
// entry in the data/ directory from the input archives.
class CustomOutputJar : public OutputJar {
public:
~CustomOutputJar() override {}
void ExtraHandler(const std::string & /*input_jar_path*/, const CDH *cdh,
const std::string *input_jar_aux_label) override {
auto file_name = cdh->file_name();
auto file_name_length = cdh->file_name_length();
if (file_name_length > 0 && file_name[file_name_length - 1] != '/' &&
begins_with(file_name, file_name_length, "tools/singlejar/data/")) {
// The contents of the data/<FILE> on the output is the
// concatenation of the data/<FILE> files from all inputs.
std::string metadata_file_path(file_name, file_name_length);
if (NewEntry(metadata_file_path)) {
ExtraCombiner(metadata_file_path, new Concatenator(metadata_file_path));
}
}
}
};
class OutputJarSimpleTest : public ::testing::Test {
protected:
void SetUp() override { runfiles.reset(Runfiles::CreateForTest()); }
void CreateOutput(const string &out_path, const std::vector<string> &args) {
const char *option_list[100] = {"--output", out_path.c_str()};
int nargs = 2;
for (auto &arg : args) {
if (arg.empty()) {
continue;
}
option_list[nargs++] = arg.c_str();
if (arg.find(' ') == string::npos) {
fprintf(stderr, " '%s'", arg.c_str());
} else {
fprintf(stderr, " %s", arg.c_str());
}
}
fprintf(stderr, "\n");
options_.ParseCommandLine(nargs, option_list);
ASSERT_EQ(0, output_jar_.Doit(&options_));
EXPECT_EQ(0, VerifyZip(out_path));
}
string CompressionOptionsTestingJar(const string &compression_option) {
string cp_res_path =
CreateTextFile("cp_res", "line1\nline2\nline3\nline4\n");
string out_path = OutputFilePath("out.jar");
CreateOutput(
out_path,
{compression_option, "--sources",
runfiles
->Rlocation(
"io_bazel/src/tools/singlejar/libtest1.jar")
.c_str(),
runfiles
->Rlocation(
"io_bazel/src/tools/singlejar/stored.jar")
.c_str(),
"--resources", cp_res_path, "--deploy_manifest_lines",
"property1: value1", "property2: value2"});
return out_path;
}
OutputJar output_jar_;
Options options_;
std::unique_ptr<Runfiles> runfiles;
};
// No inputs at all.
TEST_F(OutputJarSimpleTest, Empty) {
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path, {});
InputJar input_jar;
ASSERT_TRUE(input_jar.Open(out_path));
int entry_count = 0;
const LH *lh;
const CDH *cdh;
const uint8_t cafe_extra_field[] = {0xFE, 0xCA, 0, 0};
while ((cdh = input_jar.NextEntry(&lh))) {
++entry_count;
ASSERT_TRUE(cdh->is()) << "No expected tag in the Central Directory Entry.";
ASSERT_NE(nullptr, lh) << "No local header.";
ASSERT_TRUE(lh->is()) << "No expected tag in the Local Header.";
EXPECT_EQ(lh->file_name_string(), cdh->file_name_string());
if (!cdh->no_size_in_local_header()) {
EXPECT_EQ(lh->compressed_file_size(), cdh->compressed_file_size())
<< "Entry: " << lh->file_name_string();
EXPECT_EQ(lh->uncompressed_file_size(), cdh->uncompressed_file_size())
<< "Entry: " << cdh->file_name_string();
}
// Verify that each entry has a reasonable timestamp.
EXPECT_EQ(lh->last_mod_file_date(), cdh->last_mod_file_date())
<< "Entry: " << lh->file_name_string();
EXPECT_EQ(lh->last_mod_file_time(), cdh->last_mod_file_time())
<< "Entry: " << lh->file_name_string();
uint16_t dos_time = lh->last_mod_file_time();
uint16_t dos_date = lh->last_mod_file_date();
// Current time, rounded to even number of seconds because MSDOS timestamp
// does this, too.
time_t now = (time(nullptr) + 1) & ~1;
struct tm tm_now;
localtime_r(&now, &tm_now);
char now_time_str[50];
strftime(now_time_str, sizeof(now_time_str), "%c", &tm_now);
// Unpack MSDOS file timestamp. See the comment about its format in
// output_jar.cc.
struct tm tm;
tm.tm_sec = (dos_time & 31) << 1;
tm.tm_min = (dos_time >> 5) & 63;
tm.tm_hour = (dos_time >> 11) & 31;
tm.tm_mday = (dos_date & 31);
tm.tm_mon = ((dos_date >> 5) & 15) - 1;
tm.tm_year = ((dos_date >> 9) & 127) + 80;
tm.tm_isdst = tm_now.tm_isdst;
time_t entry_time = mktime(&tm);
char entry_time_str[50];
strftime(entry_time_str, sizeof(entry_time_str), "%c", &tm);
// Without --normalize option all the entries should have reasonably
// current timestamp (which we arbitrarily choose to be <5 minutes).
EXPECT_GE(now, entry_time) << now_time_str << " vs. " << entry_time_str;
EXPECT_LE(now, entry_time + 300) << now_time_str << " vs. "
<< entry_time_str;
// The first entry should be for the META-INF/ directory, and it should
// contain a single extra field 0xCAFE. Although
// https://bugs.openjdk.java.net/browse/JDK-6808540 claims that this extra
// field is optional, 'file' utility in Linux relies on to distinguish
// jar from zip.
if (entry_count == 1) {
ASSERT_EQ("META-INF/", lh->file_name_string());
ASSERT_EQ(4, lh->extra_fields_length());
ASSERT_EQ(0, memcmp(cafe_extra_field, lh->extra_fields(), 4));
ASSERT_EQ(4, cdh->extra_fields_length());
ASSERT_EQ(0, memcmp(cafe_extra_field, cdh->extra_fields(), 4));
}
}
input_jar.Close();
string manifest = GetEntryContents(out_path, "META-INF/MANIFEST.MF");
EXPECT_EQ(
"Manifest-Version: 1.0\r\n"
"Created-By: singlejar\r\n"
"\r\n",
manifest);
string build_properties = GetEntryContents(out_path, "build-data.properties");
EXPECT_PRED2(HasSubstr, build_properties, "build.target=");
}
// Source jars.
TEST_F(OutputJarSimpleTest, Source) {
string out_path = OutputFilePath("out.jar");
CreateOutput(
out_path,
{"--sources",
runfiles
->Rlocation(
"io_bazel/src/tools/singlejar/libtest1.jar")
.c_str(),
runfiles
->Rlocation(
"io_bazel/src/tools/singlejar/libtest2.jar")
.c_str()});
InputJar input_jar;
ASSERT_TRUE(input_jar.Open(out_path));
const LH *lh;
const CDH *cdh;
int file_count = 0;
while ((cdh = input_jar.NextEntry(&lh))) {
ASSERT_TRUE(cdh->is()) << "No expected tag in the Central Directory Entry.";
ASSERT_NE(nullptr, lh) << "No local header.";
ASSERT_TRUE(lh->is()) << "No expected tag in the Local Header.";
EXPECT_EQ(lh->file_name_string(), cdh->file_name_string());
if (!cdh->no_size_in_local_header()) {
EXPECT_EQ(lh->compressed_file_size(), cdh->compressed_file_size())
<< "Entry: " << lh->file_name_string();
EXPECT_EQ(lh->uncompressed_file_size(), cdh->uncompressed_file_size())
<< "Entry: " << cdh->file_name_string();
}
if (lh->file_name()[lh->file_name_length() - 1] != '/') {
++file_count;
}
}
ASSERT_LE(4, file_count);
input_jar.Close();
}
// Verify --java_launcher argument
TEST_F(OutputJarSimpleTest, JavaLauncher) {
string out_path = OutputFilePath("out.jar");
std::string launcher_path = runfiles->Rlocation(
"io_bazel/src/tools/singlejar/libtest1.jar");
CreateOutput(out_path, {"--java_launcher", launcher_path});
// check that the offset of the first entry equals launcher size.
InputJar input_jar;
ASSERT_TRUE(input_jar.Open(out_path.c_str()));
const LH *lh;
const CDH *cdh;
cdh = input_jar.NextEntry(&lh);
ASSERT_NE(nullptr, cdh);
struct stat statbuf;
ASSERT_EQ(0, stat(launcher_path.c_str(), &statbuf));
EXPECT_TRUE(cdh->is());
EXPECT_TRUE(lh->is());
EXPECT_EQ(static_cast<uint64_t>(statbuf.st_size), cdh->local_header_offset());
input_jar.Close();
}
// --cds_archive option
TEST_F(OutputJarSimpleTest, CDSArchive) {
string out_path = OutputFilePath("out.jar");
string launcher_path = CreateTextFile("launcher", "Dummy");
string cds_archive_path = CreateTextFile("classes.jsa", "Dummy");
CreateOutput(out_path, {"--java_launcher", launcher_path,
"--cds_archive", cds_archive_path});
// check META-INF/MANIFEST.MF attribute
string manifest = GetEntryContents(out_path, "META-INF/MANIFEST.MF");
size_t pagesize;
#ifndef _WIN32
pagesize = sysconf(_SC_PAGESIZE);
#else
SYSTEM_INFO si;
GetSystemInfo(&si);
pagesize = si.dwPageSize;
#endif
char attr[128];
snprintf(attr, sizeof(attr), "Jsa-Offset: %ld", pagesize);
EXPECT_PRED2(HasSubstr, manifest, attr);
// check build-data.properties entry
string build_properties = GetEntryContents(out_path, "build-data.properties");
char prop[4096];
snprintf(prop, sizeof(prop), "\ncds.archive=%s\n",
cds_archive_path.c_str());
EXPECT_PRED2(HasSubstr, build_properties, prop);
}
// --jdk_lib_modules option
TEST_F(OutputJarSimpleTest, JDKLibModules) {
string out_path = OutputFilePath("out.jar");
string launcher_path = CreateTextFile("launcher", "Dummy");
string jdk_lib_modules_path = CreateTextFile("modules", "Dummy");
CreateOutput(out_path, {"--java_launcher", launcher_path,
"--jdk_lib_modules", jdk_lib_modules_path});
// Test META-INF/MANIFEST.MF attributes.
string manifest = GetEntryContents(out_path, "META-INF/MANIFEST.MF");
size_t pagesize;
#ifndef _WIN32
pagesize = sysconf(_SC_PAGESIZE);
#else
SYSTEM_INFO si;
GetSystemInfo(&si);
pagesize = si.dwPageSize;
#endif
struct stat statbuf;
stat(jdk_lib_modules_path.c_str(), &statbuf);
size_t modules_size = statbuf.st_size;
char offset_attr[128];
snprintf(offset_attr, sizeof(offset_attr),
"JDK-Lib-Modules-Offset: %ld", pagesize);
EXPECT_PRED2(HasSubstr, manifest, offset_attr);
char size_attr[128];
snprintf(size_attr, sizeof(size_attr),
"JDK-Lib-Modules-Size: %ld", modules_size);
EXPECT_PRED2(HasSubstr, manifest, size_attr);
}
// --cds_archive & --jdk_lib_modules options
TEST_F(OutputJarSimpleTest, CDSAndJDKLibModules) {
string cds_data = "cafebabe";
string modules_data = "deadbeef";
string out_path = OutputFilePath("out.jar");
string launcher_path = CreateTextFile("launcher", "Dummy");
string cds_archive_path = CreateTextFile("classes.jsa", cds_data.c_str());
string jdk_lib_modules_path = CreateTextFile("modules", modules_data.c_str());
CreateOutput(out_path, {"--java_launcher", launcher_path,
"--cds_archive", cds_archive_path,
"--jdk_lib_modules", jdk_lib_modules_path});
FILE *fp = fopen(out_path.c_str(), "r");
ASSERT_NE(nullptr, fp);
// Test META-INF/MANIFEST.MF attributes.
string manifest = GetEntryContents(out_path, "META-INF/MANIFEST.MF");
size_t pagesize;
#ifndef _WIN32
pagesize = sysconf(_SC_PAGESIZE);
#else
SYSTEM_INFO si;
GetSystemInfo(&si);
pagesize = si.dwPageSize;
#endif
size_t page_aligned_cds_offset = pagesize;
char buf[8];
size_t buf_len = sizeof(buf);
struct stat statbuf;
stat(jdk_lib_modules_path.c_str(), &statbuf);
size_t modules_size = statbuf.st_size;
char cds_attr[128];
snprintf(cds_attr, sizeof(cds_attr), "Jsa-Offset: %ld",
page_aligned_cds_offset);
EXPECT_PRED2(HasSubstr, manifest, cds_attr);
fseek(fp, page_aligned_cds_offset, 0);
fread(buf, 1, buf_len, fp);
ASSERT_EQ(cds_data, string(buf, buf_len));
size_t page_aligned_modules_offset = pagesize * 2;
char modules_offset_attr[128];
snprintf(modules_offset_attr, sizeof(modules_offset_attr),
"JDK-Lib-Modules-Offset: %ld",
page_aligned_modules_offset);
EXPECT_PRED2(HasSubstr, manifest, modules_offset_attr);
char modules_size_attr[128];
snprintf(modules_size_attr, sizeof(modules_size_attr),
"JDK-Lib-Modules-Size: %ld", modules_size);
EXPECT_PRED2(HasSubstr, manifest, modules_size_attr);
fseek(fp, page_aligned_modules_offset, 0);
fread(buf, 1, buf_len, fp);
ASSERT_EQ(modules_data, string(buf, buf_len));
fclose(fp);
}
// --main_class option.
TEST_F(OutputJarSimpleTest, MainClass) {
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path, {"--main_class", "com.google.my.Main"});
string manifest = GetEntryContents(out_path, "META-INF/MANIFEST.MF");
EXPECT_EQ(
"Manifest-Version: 1.0\r\n"
"Created-By: singlejar\r\n"
"Main-Class: com.google.my.Main\r\n"
"\r\n",
manifest);
}
// --output_jar_creator option
TEST_F(OutputJarSimpleTest, CreatedByFieldTest) {
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path,
{"--output_jar_creator", "SingleJarTestValue 123.456"});
string manifest = GetEntryContents(out_path, "META-INF/MANIFEST.MF");
EXPECT_EQ(
"Manifest-Version: 1.0\r\n"
"Created-By: SingleJarTestValue 123.456\r\n"
"\r\n",
manifest);
}
// --deploy_manifest_lines option.
TEST_F(OutputJarSimpleTest, DeployManifestLines) {
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path,
{"--deploy_manifest_lines", "property1: foo", "property2: bar"});
string manifest = GetEntryContents(out_path, "META-INF/MANIFEST.MF");
EXPECT_EQ(
"Manifest-Version: 1.0\r\n"
"Created-By: singlejar\r\n"
"property1: foo\r\n"
"property2: bar\r\n"
"\r\n",
manifest);
}
// --extra_build_info option
TEST_F(OutputJarSimpleTest, ExtraBuildInfo) {
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path, {"--extra_build_info", "property1=value1",
"--extra_build_info", "property2=value2"});
string build_properties = GetEntryContents(out_path, "build-data.properties");
EXPECT_PRED2(HasSubstr, build_properties, "\nproperty1=value1\n");
EXPECT_PRED2(HasSubstr, build_properties, "\nproperty2=value2\n");
}
// --build_info_file and --extra_build_info options.
TEST_F(OutputJarSimpleTest, BuildInfoFile) {
string build_info_path1 =
CreateTextFile("buildinfo1", "property11=value11\nproperty12=value12\n");
string build_info_path2 =
CreateTextFile("buildinfo2", "property21=value21\nproperty22=value22\n");
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path, {"--build_info_file", build_info_path1,
"--extra_build_info", "property=value",
"--build_info_file", build_info_path2.c_str()});
string build_properties = GetEntryContents(out_path, "build-data.properties");
EXPECT_PRED2(HasSubstr, build_properties, "property11=value11\n");
EXPECT_PRED2(HasSubstr, build_properties, "property12=value12\n");
EXPECT_PRED2(HasSubstr, build_properties, "property21=value21\n");
EXPECT_PRED2(HasSubstr, build_properties, "property22=value22\n");
EXPECT_PRED2(HasSubstr, build_properties, "property=value\n");
}
// --resources option.
TEST_F(OutputJarSimpleTest, Resources) {
string res11_path = CreateTextFile("res11", "res11.line1\nres11.line2\n");
string res11_spec = res11_path + ":res1";
string res12_path = CreateTextFile("res12", "res12.line1\nres12.line2\n");
string res12_spec = res12_path + ":res1";
string res2_path = CreateTextFile("res2", "res2.line1\nres2.line2\n");
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path, {"--resources", res11_spec, res12_spec, res2_path});
// The output should have 'res1' entry containing the concatenation of the
// 'res11' and 'res12' files.
string res1 = GetEntryContents(out_path, "res1");
EXPECT_EQ("res11.line1\nres11.line2\nres12.line1\nres12.line2\n", res1);
// The output should have res2 path entry and contents.
string res2 = GetEntryContents(out_path, res2_path);
EXPECT_EQ("res2.line1\nres2.line2\n", res2);
}
TEST_F(OutputJarSimpleTest, ResourcesParentDirectories) {
string res1_path = CreateTextFile("res1", "res1.line1\nres1.line2\n");
string res2_path = CreateTextFile("res2", "res2.line1\nres2.line2\n");
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path, {"--exclude_build_data", "--resources",
res1_path + ":the/resources/res1",
res2_path + ":the/resources2/res2"});
string res1 = GetEntryContents(out_path, "the/resources/res1");
EXPECT_EQ("res1.line1\nres1.line2\n", res1);
string res2 = GetEntryContents(out_path, "the/resources2/res2");
EXPECT_EQ("res2.line1\nres2.line2\n", res2);
// The output should contain entries for parent directories
std::vector<string> expected_entries(
{"META-INF/", "META-INF/MANIFEST.MF", "the/", "the/resources/",
"the/resources/res1", "the/resources2/", "the/resources2/res2"});
std::vector<string> jar_entries;
InputJar input_jar;
ASSERT_TRUE(input_jar.Open(out_path));
const LH *lh;
const CDH *cdh;
while ((cdh = input_jar.NextEntry(&lh))) {
jar_entries.push_back(cdh->file_name_string());
}
input_jar.Close();
EXPECT_EQ(expected_entries, jar_entries);
}
TEST_F(OutputJarSimpleTest, ResourcesDirectories) {
string dir_path = OutputFilePath("resource_dir");
blaze_util::MakeDirectories(dir_path, 0777);
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path,
{"--exclude_build_data", "--resources", dir_path + ":the/dir"});
// The output should contain entries for the directory
std::vector<string> expected_entries({
"META-INF/", "META-INF/MANIFEST.MF", "the/", "the/dir/",
});
std::vector<string> jar_entries;
InputJar input_jar;
ASSERT_TRUE(input_jar.Open(out_path));
const LH *lh;
const CDH *cdh;
while ((cdh = input_jar.NextEntry(&lh))) {
jar_entries.push_back(cdh->file_name_string());
}
input_jar.Close();
EXPECT_EQ(expected_entries, jar_entries);
}
// --classpath_resources
TEST_F(OutputJarSimpleTest, ClasspathResources) {
string res1_path = OutputFilePath("cp_res");
ASSERT_TRUE(blaze_util::WriteFile("line1\nline2\n", res1_path));
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path, {"--classpath_resources", res1_path.c_str()});
string res = GetEntryContents(out_path, "cp_res");
EXPECT_EQ("line1\nline2\n", res);
}
// Duplicate entries for --resources or --classpath_resources
TEST_F(OutputJarSimpleTest, DuplicateResources) {
string cp_res_path = CreateTextFile("cp_res", "line1\nline2\n");
string res1_path = CreateTextFile("res1", "resline1\nresline2\n");
string res1_spec = res1_path + ":foo";
string res2_path = CreateTextFile("res2", "line3\nline4\n");
string res2_spec = res2_path + ":foo";
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path,
{"--warn_duplicate_resources", "--resources", res1_spec,
res2_spec, "--classpath_resources", cp_res_path, cp_res_path});
string cp_res = GetEntryContents(out_path, "cp_res");
EXPECT_EQ("line1\nline2\n", cp_res);
string foo = GetEntryContents(out_path, "foo");
EXPECT_EQ("resline1\nresline2\n", foo);
}
// Extra combiners
TEST_F(OutputJarSimpleTest, ExtraCombiners) {
string resolvedLibDataPath1 = runfiles->Rlocation(kPathLibData1);
string resolvedLibDataPath2 = runfiles->Rlocation(kPathLibData2);
string out_path = OutputFilePath("out.jar");
const char kEntry[] = "tools/singlejar/data/extra_file1";
output_jar_.ExtraCombiner(kEntry, new Concatenator(kEntry));
CreateOutput(out_path, {"--sources", resolvedLibDataPath1.c_str(),
resolvedLibDataPath2.c_str()});
string contents1 = GetEntryContents(resolvedLibDataPath1.c_str(), kEntry);
string contents2 = GetEntryContents(resolvedLibDataPath2.c_str(), kEntry);
EXPECT_EQ(contents1 + contents2, GetEntryContents(out_path, kEntry));
}
// Test ExtraHandler override.
TEST_F(OutputJarSimpleTest, ExtraHandler) {
string resolvedLibDataPath1 = runfiles->Rlocation(kPathLibData1);
string resolvedLibDataPath2 = runfiles->Rlocation(kPathLibData2);
string out_path = OutputFilePath("out.jar");
const char kEntry[] = "tools/singlejar/data/extra_file1";
const char *option_list[] = {"--output", out_path.c_str(), "--sources",
resolvedLibDataPath1.c_str(),
resolvedLibDataPath2.c_str()};
CustomOutputJar custom_output_jar;
options_.ParseCommandLine(arraysize(option_list), option_list);
ASSERT_EQ(0, custom_output_jar.Doit(&options_));
EXPECT_EQ(0, VerifyZip(out_path));
string contents1 = GetEntryContents(resolvedLibDataPath1.c_str(), kEntry);
string contents2 = GetEntryContents(resolvedLibDataPath2.c_str(), kEntry);
EXPECT_EQ(contents1 + contents2, GetEntryContents(out_path, kEntry));
}
// --include_headers
TEST_F(OutputJarSimpleTest, IncludeHeaders) {
string resolvedLibDataPath1 = runfiles->Rlocation(kPathLibData1);
string out_path = OutputFilePath("out.jar");
CreateOutput(
out_path,
{"--sources",
runfiles
->Rlocation(
"io_bazel/src/tools/singlejar/libtest1.jar")
.c_str(),
resolvedLibDataPath1.c_str(), "--include_prefixes",
"tools/singlejar/data"});
std::vector<string> expected_entries(
{"META-INF/", "META-INF/MANIFEST.MF", "build-data.properties",
"tools/singlejar/data/", "tools/singlejar/data/extra_file1",
"tools/singlejar/data/extra_file2"});
std::vector<string> jar_entries;
InputJar input_jar;
ASSERT_TRUE(input_jar.Open(out_path));
const LH *lh;
const CDH *cdh;
while ((cdh = input_jar.NextEntry(&lh))) {
jar_entries.push_back(cdh->file_name_string());
}
input_jar.Close();
EXPECT_EQ(expected_entries, jar_entries);
}
// --normalize
TEST_F(OutputJarSimpleTest, Normalize) {
// Creates output jar containing entries from all possible sources:
// * archives created by java_library rule, by jar tool, by zip
// * resource files
// * classpath resource files
// *
string out_path = OutputFilePath("out.jar");
string testjar_path = OutputFilePath("testinput.jar");
{
std::string jar_tool_path = runfiles->Rlocation(JAR_TOOL_PATH);
string textfile_path = CreateTextFile("jar_testinput.txt", "jar_inputtext");
string classfile_path = CreateTextFile("JarTestInput.class", "Dummy");
unlink(testjar_path.c_str());
ASSERT_EQ(
0, RunCommand(jar_tool_path.c_str(), "-cf", testjar_path.c_str(),
textfile_path.c_str(), classfile_path.c_str(), nullptr));
}
string testzip_path = OutputFilePath("testinput.zip");
{
string textfile_path = CreateTextFile("zip_testinput.txt", "zip_inputtext");
string classfile_path = CreateTextFile("ZipTestInput.class", "Dummy");
unlink(testzip_path.c_str());
ASSERT_EQ(
0, RunCommand("zip", "-m", testzip_path.c_str(), textfile_path.c_str(),
classfile_path.c_str(), nullptr));
}
string resource_path = CreateTextFile("resource", "resource_text");
string cp_resource_path = CreateTextFile("cp_resource", "cp_resource_text");
// TODO(asmundak): check the following generated entries, too:
// * services
// * spring.schemas
// * spring.handlers
// * protobuf.meta
// * extra combiner
CreateOutput(
out_path,
{"--normalize", "--sources",
runfiles
->Rlocation(
"io_bazel/src/tools/singlejar/libtest1.jar")
.c_str(),
testjar_path, testzip_path, "--resources", resource_path,
"--classpath_resources", cp_resource_path});
// Scan all entries, verify that *.class entries have timestamp
// 01/01/2010 00:00:02 and the rest have the timestamp of 01/01/2010 00:00:00.
InputJar input_jar;
ASSERT_TRUE(input_jar.Open(out_path));
const LH *lh;
const CDH *cdh;
while ((cdh = input_jar.NextEntry(&lh))) {
string entry_name = cdh->file_name_string();
EXPECT_EQ(lh->last_mod_file_date(), cdh->last_mod_file_date())
<< entry_name << " modification date";
EXPECT_EQ(lh->last_mod_file_time(), cdh->last_mod_file_time())
<< entry_name << " modification time";
EXPECT_EQ(15393, cdh->last_mod_file_date())
<< entry_name << " modification date should be 01/01/2010";
auto n = entry_name.size() - strlen(".class");
if (0 == strcmp(entry_name.c_str() + n, ".class")) {
EXPECT_EQ(1, cdh->last_mod_file_time())
<< entry_name
<< " modification time for .class entry should be 00:00:02";
} else {
EXPECT_EQ(0, cdh->last_mod_file_time())
<< entry_name
<< " modification time for non .class entry should be 00:00:00";
}
// Zip creates Unix timestamps, too. Check that normalization removes them.
ASSERT_EQ(nullptr, cdh->unix_time_extra_field())
<< entry_name << ": CDH should not have Unix Time extra field";
ASSERT_EQ(nullptr, lh->unix_time_extra_field())
<< entry_name << ": LH should not have Unix Time extra field";
}
input_jar.Close();
}
// --add_missing_directories
TEST_F(OutputJarSimpleTest, AddMissingDirectories) {
string out_path = OutputFilePath("out.jar");
string testjar_path = OutputFilePath("testinput.jar");
std::string jar_tool_path = runfiles->Rlocation(JAR_TOOL_PATH);
string textfile_path =
CreateTextFile("a/b/jar_testinput.txt", "jar_inputtext");
string classfile1_path = CreateTextFile("a/c/Foo.class", "Dummy");
string classfile2_path = CreateTextFile("c/Foo.class", "Dummy");
string classfile3_path = CreateTextFile("c/Bar.class", "Dummy");
unlink(testjar_path.c_str());
ASSERT_EQ(
0, RunCommand(jar_tool_path.c_str(), "-cf", testjar_path.c_str(),
textfile_path.c_str(), classfile1_path.c_str(),
classfile2_path.c_str(), classfile3_path.c_str(), nullptr));
CreateOutput(out_path, {"--normalize", "--add_missing_directories",
"--sources", testjar_path});
// Scan all entries, verify that *.class entries have timestamp
// 01/01/2010 00:00:02 and the rest have the timestamp of 01/01/2010 00:00:00.
InputJar input_jar;
ASSERT_TRUE(input_jar.Open(out_path));
const LH *lh;
const CDH *cdh;
bool seen_a = false;
bool seen_ab = false;
bool seen_ac = false;
bool seen_c = false;
while ((cdh = input_jar.NextEntry(&lh))) {
string entry_name = cdh->file_name_string();
EXPECT_EQ(lh->last_mod_file_date(), cdh->last_mod_file_date())
<< entry_name << " modification date";
EXPECT_EQ(lh->last_mod_file_time(), cdh->last_mod_file_time())
<< entry_name << " modification time";
EXPECT_EQ(15393, cdh->last_mod_file_date())
<< entry_name << " modification date should be 01/01/2010";
auto n = entry_name.size() - strlen(".class");
if (0 == strcmp(entry_name.c_str() + n, ".class")) {
EXPECT_EQ(1, cdh->last_mod_file_time())
<< entry_name
<< " modification time for .class entry should be 00:00:02";
} else {
EXPECT_EQ(0, cdh->last_mod_file_time())
<< entry_name
<< " modification time for non .class entry should be 00:00:00";
}
// Zip creates Unix timestamps, too. Check that normalization removes them.
ASSERT_EQ(nullptr, cdh->unix_time_extra_field())
<< entry_name << ": CDH should not have Unix Time extra field";
ASSERT_EQ(nullptr, lh->unix_time_extra_field())
<< entry_name << ": LH should not have Unix Time extra field";
if (EndsWith(entry_name, "/a/")) {
EXPECT_FALSE(seen_a) << "a/ duplicate";
seen_a = true;
} else if (EndsWith(entry_name, "/a/b/")) {
EXPECT_FALSE(seen_ab) << "a/b/ duplicate";
seen_ab = true;
} else if (EndsWith(entry_name, "/a/c/")) {
EXPECT_FALSE(seen_ac) << "a/c/ duplicate";
seen_ac = true;
} else if (EndsWith(entry_name, "/c/")) {
EXPECT_FALSE(seen_c) << "c/ duplicate";
seen_c = true;
}
}
EXPECT_TRUE(seen_a) << "a/ entry missing";
EXPECT_TRUE(seen_ab) << "a/b/ entry missing";
EXPECT_TRUE(seen_ac) << "a/c/ entry missing";
EXPECT_TRUE(seen_c) << "c/ entry missing";
input_jar.Close();
}
// The files names META-INF/services/<something> are concatenated.
// The files named META-INF/spring.handlers are concatenated.
// The files named META-INF/spring.schemas are concatenated.
TEST_F(OutputJarSimpleTest, Services) {
CreateTextFile("META-INF/services/spi.DateProvider",
"my.DateProviderImpl1\n");
CreateTextFile("META-INF/services/spi.TimeProvider",
"my.TimeProviderImpl1\n");
CreateTextFile("META-INF/spring.handlers", "handler1\n");
CreateTextFile("META-INF/spring.schemas", "schema1\n");
// We have to be in the output directory if we want to have entries in the
// archive to start with META-INF. The resulting zip will contain 4 entries:
// META-INF/services/spi.DateProvider
// META-INF/services/spi.TimeProvider
// META-INF/spring.handlers
// META-INF/spring.schemas
string out_dir = OutputFilePath("");
ASSERT_EQ(0, RunCommand("cd", out_dir.c_str(), CMD_SEPARATOR, "zip", "-mr",
"testinput1.zip", "META-INF", nullptr));
string zip1_path = OutputFilePath("testinput1.zip");
// Create the second zip, with 3 files:
// META-INF/services/spi.DateProvider.
// META-INF/spring.handlers
// META-INF/spring.schemas
CreateTextFile("META-INF/services/spi.DateProvider",
"my.DateProviderImpl2\n");
CreateTextFile("META-INF/spring.handlers", "handler2\n");
CreateTextFile("META-INF/spring.schemas", "schema2\n");
ASSERT_EQ(0, RunCommand("cd ", out_dir.c_str(), CMD_SEPARATOR, "zip", "-mr",
"testinput2.zip", "META-INF", nullptr));
string zip2_path = OutputFilePath("testinput2.zip");
// The output jar should contain two service entries. The contents of the
// META-INF/services/spi.DateProvider should be the concatenation of the
// contents of this entry from both archives. And it should also contain
// spring.handlers and spring.schemas entries.
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path, {"--sources", zip1_path, zip2_path});
EXPECT_EQ("my.DateProviderImpl1\n" "my.DateProviderImpl2\n",
GetEntryContents(out_path, "META-INF/services/spi.DateProvider"));
EXPECT_EQ("my.TimeProviderImpl1\n",
GetEntryContents(out_path, "META-INF/services/spi.TimeProvider"));
EXPECT_EQ("schema1\n" "schema2\n",
GetEntryContents(out_path, "META-INF/spring.schemas"));
EXPECT_EQ("handler1\n" "handler2\n",
GetEntryContents(out_path, "META-INF/spring.handlers"));
}
// Test that in the absence of the compression option all the plain files in
// the output archive are not compressed but just stored.
TEST_F(OutputJarSimpleTest, NoCompressionOption) {
string out_path = CompressionOptionsTestingJar("");
InputJar input_jar;
ASSERT_TRUE(input_jar.Open(out_path));
const LH *lh;
const CDH *cdh;
while ((cdh = input_jar.NextEntry(&lh))) {
string entry_name = lh->file_name_string();
// Each file entry is compressed, each directory entry is uncompressed.
EXPECT_EQ(lh->compression_method(), cdh->compression_method());
EXPECT_EQ(Z_NO_COMPRESSION, lh->compression_method())
<< "Entry " << entry_name << " should be stored.";
}
input_jar.Close();
}
// Test --compression option. If enabled, all file entries are compressed
// while all directory entries remain uncompressed.
TEST_F(OutputJarSimpleTest, CompressionOption) {
string out_path = CompressionOptionsTestingJar("--compression");
InputJar input_jar;
ASSERT_TRUE(input_jar.Open(out_path));
const LH *lh;
const CDH *cdh;
while ((cdh = input_jar.NextEntry(&lh))) {
string entry_name = lh->file_name_string();
// Each file entry is compressed, each directory entry is uncompressed.
EXPECT_EQ(lh->compression_method(), cdh->compression_method());
if (lh->file_name()[lh->file_name_length() - 1] != '/') {
EXPECT_EQ(Z_DEFLATED, lh->compression_method())
<< "File entry " << entry_name << " should be compressed.";
} else {
EXPECT_EQ(Z_NO_COMPRESSION, lh->compression_method())
<< "Directory entry " << entry_name << " should be stored.";
}
}
input_jar.Close();
}
// Test --dontchangecompression option. If enabled, existing file entries are
// copied as is, and created entries are compressed.
// Test --compression option. If enabled, all file entries are compressed
// while all directory entries remain uncompressed.
TEST_F(OutputJarSimpleTest, DontChangeCompressionOption) {
string out_path = CompressionOptionsTestingJar("--dont_change_compression");
InputJar input_jar;
ASSERT_TRUE(input_jar.Open(out_path));
const LH *lh;
const CDH *cdh;
std::string kStoredEntry = DATA_DIR_TOP "src/tools/singlejar/output_jar.cc";
while ((cdh = input_jar.NextEntry(&lh))) {
string entry_name = lh->file_name_string();
EXPECT_EQ(lh->compression_method(), cdh->compression_method());
if (lh->file_name()[lh->file_name_length() - 1] != '/') {
// All created file entries are compressed, and so are all the file
// entries from the input jar created by the java_library rule. Only
// the file entries from the 'stored_jar' should be uncompressed, and
// it contains a single one:
if (entry_name == kStoredEntry) {
EXPECT_EQ(Z_NO_COMPRESSION, lh->compression_method())
<< "File entry " << entry_name << " should be stored.";
} else {
EXPECT_EQ(Z_DEFLATED, lh->compression_method())
<< "File entry " << entry_name << " should be compressed.";
}
} else {
EXPECT_EQ(Z_NO_COMPRESSION, lh->compression_method())
<< "Directory entry " << entry_name << " should be stored.";
}
}
input_jar.Close();
}
const char kBuildDataFile[] = "build-data.properties";
// Test --exclude_build_data option when none of the source archives contain
// build-data.properties file: no such file in the output archive.
TEST_F(OutputJarSimpleTest, ExcludeBuildData1) {
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path, {"--exclude_build_data"});
InputJar input_jar;
ASSERT_TRUE(input_jar.Open(out_path));
const LH *lh;
const CDH *cdh;
while ((cdh = input_jar.NextEntry(&lh))) {
string entry_name = lh->file_name_string();
EXPECT_NE(kBuildDataFile, lh->file_name_string());
}
input_jar.Close();
}
// Test --exclude_build_data option when a source archive contains
// build-data.properties file, it should be then copied to the output.
TEST_F(OutputJarSimpleTest, ExcludeBuildData2) {
string out_dir = OutputFilePath("");
string testzip_path = OutputFilePath("testinput.zip");
string buildprop_path = CreateTextFile(kBuildDataFile, "build: foo");
unlink(testzip_path.c_str());
ASSERT_EQ(0, RunCommand("cd ", out_dir.c_str(), CMD_SEPARATOR, "zip", "-m",
"testinput.zip", kBuildDataFile, nullptr));
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path, {"--exclude_build_data", "--sources", testzip_path});
EXPECT_EQ("build: foo", GetEntryContents(out_path, kBuildDataFile));
}
// Test that the entries with suffixes in --nocompressed_suffixes are
// not compressed. This applies both to the source archives' entries and
// standalone files.
TEST_F(OutputJarSimpleTest, Nocompress) {
string res1_path =
CreateTextFile("resource.foo", "line1\nline2\nline3\nline4\n");
string res2_path =
CreateTextFile("resource.bar", "line1\nline2\nline3\nline4\n");
string out_path = OutputFilePath("out.jar");
CreateOutput(
out_path,
{"--compression", "--sources",
runfiles
->Rlocation(
"io_bazel/src/tools/singlejar/libtest1.jar")
.c_str(),
"--resources", res1_path, res2_path, "--nocompress_suffixes", ".foo",
".h"});
InputJar input_jar;
ASSERT_TRUE(input_jar.Open(out_path));
const LH *lh;
const CDH *cdh;
while ((cdh = input_jar.NextEntry(&lh))) {
const char *entry_name_end = lh->file_name() + lh->file_name_length();
if (!strncmp(entry_name_end - 4, ".foo", 4) ||
!strncmp(entry_name_end - 2, ".h", 2)) {
EXPECT_EQ(Z_NO_COMPRESSION, lh->compression_method())
<< "Expected " << lh->file_name_string() << " uncompressed";
} else if (!strncmp(entry_name_end - 3, ".cc", 3) ||
!strncmp(entry_name_end - 4, ".bar", 4)) {
EXPECT_EQ(Z_DEFLATED, lh->compression_method())
<< "Expected " << lh->file_name_string() << " compressed";
}
}
input_jar.Close();
}
// --multi_release option.
TEST_F(OutputJarSimpleTest, MultiRelease) {
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path, {"--multi_release"});
string manifest = GetEntryContents(out_path, "META-INF/MANIFEST.MF");
EXPECT_EQ(
"Manifest-Version: 1.0\r\n"
"Created-By: singlejar\r\n"
"Multi-Release: true\r\n"
"\r\n",
manifest);
}
// --multi_release option doesn't override --deploy_manifest_lines.
TEST_F(OutputJarSimpleTest, MultiReleaseManifestLines) {
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path, {"--multi_release", "--deploy_manifest_lines",
"Multi-Release: false"});
string manifest = GetEntryContents(out_path, "META-INF/MANIFEST.MF");
EXPECT_EQ(
"Manifest-Version: 1.0\r\n"
"Created-By: singlejar\r\n"
"\r\n",
manifest);
}
// --hermetic_java_home
TEST_F(OutputJarSimpleTest, HermeticJavaHome) {
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path, {"--hermetic_java_home", "foo/bar/java_home"});
string manifest = GetEntryContents(out_path, "META-INF/MANIFEST.MF");
EXPECT_EQ(
"Manifest-Version: 1.0\r\n"
"Created-By: singlejar\r\n"
"Hermetic-Java-Home: foo/bar/java_home\r\n"
"\r\n",
manifest);
}
// --add_exports and --add_opens options combines with --deploy_manifest_lines.
TEST_F(OutputJarSimpleTest, AddExportsManifestLines) {
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path,
{"--add_exports", "foo/com.export", "--add_opens",
"foo/com.open", "--deploy_manifest_lines",
"Add-Exports: bar/com.export", "Add-Opens: bar/com.open"});
string manifest = GetEntryContents(out_path, "META-INF/MANIFEST.MF");
EXPECT_EQ(
"Manifest-Version: 1.0\r\n"
"Created-By: singlejar\r\n"
"Add-Exports: bar/com.export\r\n"
" foo/com.export\r\n"
"Add-Opens: bar/com.open\r\n"
" foo/com.open\r\n"
"\r\n",
manifest);
}
// Deduplicate --add_exports
TEST_F(OutputJarSimpleTest, AddExportsDuplicate) {
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path,
{"--add_exports", "foo/export", "--add_exports", "foo/export"});
string manifest = GetEntryContents(out_path, "META-INF/MANIFEST.MF");
EXPECT_EQ(
"Manifest-Version: 1.0\r\n"
"Created-By: singlejar\r\n"
"Add-Exports: foo/export\r\n"
"\r\n",
manifest);
}
// Tokenize, sort, and deduplicate existing Add-Exports lines
TEST_F(OutputJarSimpleTest, AddExportsTokenize) {
string out_path = OutputFilePath("out.jar");
CreateOutput(out_path, {"--deploy_manifest_lines",
"Add-Exports: foo/export bar/export foo/export"});
string manifest = GetEntryContents(out_path, "META-INF/MANIFEST.MF");
EXPECT_EQ(
"Manifest-Version: 1.0\r\n"
"Created-By: singlejar\r\n"
"Add-Exports: bar/export\r\n"
" foo/export\r\n"
"\r\n",
manifest);
}
} // namespace