Path: blob/master/test/jdk/tools/jar/ReproducibleJar.java
66643 views
/*1* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/*24* @test25* @bug 827676626* @requires vm.bits == 6427* @summary Test jar --date source date of entries and that jars are28* reproducible29* @modules jdk.jartool30* @run testng/othervm ReproducibleJar31*/3233import org.testng.Assert;34import org.testng.annotations.AfterMethod;35import org.testng.annotations.BeforeMethod;36import org.testng.annotations.Test;37import org.testng.annotations.DataProvider;3839import java.io.File;40import java.io.IOException;41import java.io.PrintWriter;42import java.nio.file.Files;43import java.nio.file.attribute.FileTime;44import java.util.Date;45import java.util.TimeZone;46import java.util.spi.ToolProvider;47import java.time.LocalDateTime;48import java.time.ZonedDateTime;49import java.time.ZoneId;50import java.time.ZoneOffset;51import java.time.format.DateTimeFormatter;52import java.time.Instant;53import java.util.concurrent.TimeUnit;5455public class ReproducibleJar {56private static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar")57.orElseThrow(() ->58new RuntimeException("jar tool not found")59);6061// ZipEntry's mod date has 2 seconds precision: give extra time to62// allow for e.g. rounding/truncation and networked/samba drives.63private static final long PRECISION = 10000L;6465private static final TimeZone TZ = TimeZone.getDefault();66private static final boolean DST = TZ.inDaylightTime(new Date());67private static final String UNIX_2038_ROLLOVER_TIME = "2038-01-19T03:14:07Z";68private static final Instant UNIX_2038_ROLLOVER = Instant.parse(UNIX_2038_ROLLOVER_TIME);69private static final File DIR_OUTER = new File("outer");70private static final File DIR_INNER = new File(DIR_OUTER, "inner");71private static final File FILE_INNER = new File(DIR_INNER, "foo.txt");72private static final File JAR_FILE_SOURCE_DATE1 = new File("JarEntryTimeSourceDate1.jar");73private static final File JAR_FILE_SOURCE_DATE2 = new File("JarEntryTimeSourceDate2.jar");7475// Valid --date values for jar76@DataProvider77private Object[][] validSourceDates() {78return new Object[][]{79{"1980-01-01T00:00:02+00:00"},80{"1986-06-24T01:02:03+00:00"},81{"2022-03-15T00:00:00+00:00"},82{"2022-03-15T00:00:00+06:00"},83{"2021-12-25T09:30:00-08:00[America/Los_Angeles]"},84{"2021-12-31T23:59:59Z"},85{"2024-06-08T14:24Z"},86{"2026-09-24T16:26-05:00"},87{"2038-11-26T06:06:06+00:00"},88{"2098-02-18T00:00:00-08:00"},89{"2099-12-31T23:59:59+00:00"}90};91}9293// Invalid --date values for jar94@DataProvider95private Object[][] invalidSourceDates() {96return new Object[][]{97{"1976-06-24T01:02:03+00:00"},98{"1980-01-01T00:00:01+00:00"},99{"2100-01-01T00:00:00+00:00"},100{"2138-02-18T00:00:00-11:00"},101{"2006-04-06T12:38:00"},102{"2012-08-24T16"}103};104}105106@BeforeMethod107public void runBefore() throws IOException {108runAfter();109createOuterInnerDirs();110}111112@AfterMethod113public void runAfter() {114cleanup(DIR_INNER);115cleanup(DIR_OUTER);116JAR_FILE_SOURCE_DATE1.delete();117JAR_FILE_SOURCE_DATE2.delete();118TimeZone.setDefault(TZ);119}120121/**122* Test jar tool with various valid --date <timestamps>123*/124@Test(dataProvider = "validSourceDates")125public void testValidSourceDate(String sourceDate) {126if (isInTransition()) return;127128// Test --date source date129Assert.assertEquals(JAR_TOOL.run(System.out, System.err,130"--create",131"--file", JAR_FILE_SOURCE_DATE1.getName(),132"--date", sourceDate,133DIR_OUTER.getName()), 0);134Assert.assertTrue(JAR_FILE_SOURCE_DATE1.exists());135136// Extract JAR_FILE_SOURCE_DATE1 and check last modified values137Assert.assertEquals(JAR_TOOL.run(System.out, System.err,138"--extract",139"--file", JAR_FILE_SOURCE_DATE1.getName()), 0);140Assert.assertTrue(DIR_OUTER.exists());141Assert.assertTrue(DIR_INNER.exists());142Assert.assertTrue(FILE_INNER.exists());143LocalDateTime expectedLdt = ZonedDateTime.parse(sourceDate,144DateTimeFormatter.ISO_DATE_TIME)145.withZoneSameInstant(ZoneOffset.UTC)146.toLocalDateTime();147System.out.format("Checking jar entries local date time for --date %s, is %s%n",148sourceDate, expectedLdt);149long sourceDateEpochMillis = TimeUnit.MILLISECONDS.convert(150expectedLdt.toEpochSecond(ZoneId.systemDefault().getRules()151.getOffset(expectedLdt)), TimeUnit.SECONDS);152checkFileTime(DIR_OUTER.lastModified(), sourceDateEpochMillis);153checkFileTime(DIR_INNER.lastModified(), sourceDateEpochMillis);154checkFileTime(FILE_INNER.lastModified(), sourceDateEpochMillis);155}156157/**158* Test jar tool with various invalid --date <timestamps>159*/160@Test(dataProvider = "invalidSourceDates")161public void testInvalidSourceDate(String sourceDate) {162// Negative Tests --date out of range or wrong format source date163Assert.assertNotEquals(JAR_TOOL.run(System.out, System.err,164"--create",165"--file", JAR_FILE_SOURCE_DATE1.getName(),166"--date", sourceDate,167DIR_OUTER.getName()), 0);168}169170/**171* Test jar produces deterministic reproducible output172*/173@Test(dataProvider = "validSourceDates")174public void testJarsReproducible(String sourceDate) throws IOException {175// Test jars are reproducible across timezones176TimeZone tzAsia = TimeZone.getTimeZone("Asia/Shanghai");177TimeZone tzLA = TimeZone.getTimeZone("America/Los_Angeles");178TimeZone.setDefault(tzAsia);179Assert.assertEquals(JAR_TOOL.run(System.out, System.err,180"--create",181"--file", JAR_FILE_SOURCE_DATE1.getName(),182"--date", sourceDate,183DIR_OUTER.getName()), 0);184Assert.assertTrue(JAR_FILE_SOURCE_DATE1.exists());185186try {187// Sleep 5 seconds to ensure jar timestamps might be different if they could be188Thread.sleep(5000);189} catch (InterruptedException ex) {190}191192TimeZone.setDefault(tzLA);193Assert.assertEquals(JAR_TOOL.run(System.out, System.err,194"--create",195"--file", JAR_FILE_SOURCE_DATE2.getName(),196"--date", sourceDate,197DIR_OUTER.getName()), 0);198Assert.assertTrue(JAR_FILE_SOURCE_DATE2.exists());199200// Check jars are identical201Assert.assertEquals(Files.readAllBytes(JAR_FILE_SOURCE_DATE1.toPath()),202Files.readAllBytes(JAR_FILE_SOURCE_DATE2.toPath()));203}204205/**206* Create the standard directory structure used by the test:207* outer/208* inner/209* foo.txt210*/211static void createOuterInnerDirs() throws IOException {212Assert.assertTrue(DIR_OUTER.mkdir());213Assert.assertTrue(DIR_INNER.mkdir());214try (PrintWriter pw = new PrintWriter(FILE_INNER)) {215pw.println("hello, world");216}217Assert.assertTrue(DIR_OUTER.exists());218Assert.assertTrue(DIR_INNER.exists());219Assert.assertTrue(FILE_INNER.exists());220}221222/**223* Check the extracted and original millis since Epoch file times are224* within the zip precision time period.225*/226static void checkFileTime(long now, long original) {227if (isTimeSettingChanged()) {228return;229}230231if (Math.abs(now - original) > PRECISION) {232// If original time is after UNIX 2038 32bit rollover233// and the now time is exactly the rollover time, then assume234// running on a file system that only supports to 2038 (e.g.XFS) and pass test235if (FileTime.fromMillis(original).toInstant().isAfter(UNIX_2038_ROLLOVER) &&236FileTime.fromMillis(now).toInstant().equals(UNIX_2038_ROLLOVER)) {237System.out.println("Checking file time after Unix 2038 rollover," +238" and extracted file time is " + UNIX_2038_ROLLOVER_TIME + ", " +239" Assuming restricted file system, pass file time check.");240} else {241throw new AssertionError("checkFileTime failed," +242" extracted to " + FileTime.fromMillis(now) +243", expected to be close to " + FileTime.fromMillis(original));244}245}246}247248/**249* Has the timezone or DST changed during the test?250*/251private static boolean isTimeSettingChanged() {252TimeZone currentTZ = TimeZone.getDefault();253boolean currentDST = currentTZ.inDaylightTime(new Date());254if (!currentTZ.equals(TZ) || currentDST != DST) {255System.out.println("Timezone or DST has changed during " +256"ReproducibleJar testcase execution. Test skipped");257return true;258} else {259return false;260}261}262263/**264* Is the Zone currently within the transition change period?265*/266private static boolean isInTransition() {267var inTransition = false;268var date = new Date();269var defZone = ZoneId.systemDefault();270if (defZone.getRules().getTransition(271date.toInstant().atZone(defZone).toLocalDateTime()) != null) {272System.out.println("ReproducibleJar testcase being run during Zone offset transition. Test skipped.");273inTransition = true;274}275return inTransition;276}277278/**279* Remove the directory and its contents280*/281static void cleanup(File dir) {282File[] x = dir.listFiles();283if (x != null) {284for (File f : x) {285f.delete();286}287}288dir.delete();289}290}291292293