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