Skip to content

Commit 1526dd8

Browse files
author
Justin Lu
committed
8354344: Test behavior after cut-over for future ISO 4217 currency
Reviewed-by: naoto
1 parent 8270cd0 commit 1526dd8

File tree

2 files changed

+132
-5
lines changed

2 files changed

+132
-5
lines changed

test/jdk/java/util/Currency/ValidateISO4217.java

Lines changed: 130 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2007, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2007, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -26,25 +26,52 @@
2626
* @bug 4691089 4819436 4942982 5104960 6544471 6627549 7066203 7195759
2727
* 8039317 8074350 8074351 8145952 8187946 8193552 8202026 8204269
2828
* 8208746 8209775 8264792 8274658 8283277 8296239 8321480 8334653
29+
* 8354344
2930
* @summary Validate ISO 4217 data for Currency class.
3031
* @modules java.base/java.util:open
3132
* jdk.localedata
32-
* @run junit ValidateISO4217
33+
* @library /test/lib
34+
* @run junit/othervm -DMOCKED.TIME=setup ValidateISO4217
35+
* @run main/othervm --patch-module java.base=${test.class.path}
36+
* -DMOCKED.TIME=check -Djava.util.currency.data=${test.src}/currency.properties ValidateISO4217
37+
* @run junit/othervm --patch-module java.base=${test.class.path}
38+
* -DMOCKED.TIME=true ValidateISO4217
39+
*/
40+
41+
/* The run invocation order is important. The first invocation will generate
42+
* class files for Currency that mock System.currentTimeMillis() as Long.MAX_VALUE,
43+
* which is required by the subsequent invocations. The second invocation ensures that
44+
* the module patch and mocked time are functioning correctly; it does not run any tests.
45+
* The third invocation using the modded class files via a module patch allow us
46+
* to test any cut-over dates after the transition.
47+
* Valid MOCKED.TIME values are "setup", "check", and "true".
3348
*/
3449

3550
import java.io.BufferedReader;
3651
import java.io.File;
52+
import java.io.FileOutputStream;
3753
import java.io.FileReader;
54+
import java.io.IOException;
55+
import java.lang.classfile.ClassFile;
56+
import java.lang.classfile.ClassTransform;
57+
import java.lang.classfile.CodeTransform;
58+
import java.lang.classfile.MethodTransform;
59+
import java.lang.classfile.instruction.InvokeInstruction;
60+
import java.net.URI;
61+
import java.nio.file.Files;
62+
import java.nio.file.Paths;
3863
import java.text.ParseException;
3964
import java.text.SimpleDateFormat;
4065
import java.util.ArrayList;
66+
import java.util.Arrays;
4167
import java.util.Currency;
4268
import java.util.HashSet;
4369
import java.util.List;
4470
import java.util.Locale;
4571
import java.util.Set;
4672
import java.util.StringTokenizer;
4773
import java.util.TimeZone;
74+
import java.util.stream.Stream;
4875

4976
import org.junit.jupiter.api.BeforeAll;
5077
import org.junit.jupiter.api.Test;
@@ -113,10 +140,102 @@ public class ValidateISO4217 {
113140
{"XK", "EUR", "978", "2"}, // Kosovo
114141
};
115142
private static SimpleDateFormat format = null;
143+
private static final String MODULE_PATCH_LOCATION =
144+
System.getProperty("test.classes") + "/java/util/";
145+
private static final String MOCKED_TIME = System.getProperty("MOCKED.TIME");
146+
147+
// Classes that should mock System.currentTimeMillis()
148+
private static final String[] CLASSES =
149+
Stream.concat(
150+
Stream.of("Currency.class"),
151+
Arrays.stream(Currency.class.getDeclaredClasses())
152+
.map(c -> "Currency$" + c.getSimpleName() + ".class")
153+
).toArray(String[]::new);
154+
155+
// "check" invocation only runs the main method (and not any tests) to determine if the
156+
// future time checking is correct
157+
public static void main(String[] args) {
158+
if (MOCKED_TIME.equals("check")) {
159+
// Check that the module patch class files exist
160+
checkModulePatchExists();
161+
// Check time is mocked
162+
// Override for PK=PKR in test/currency.properties is PKZ - simple
163+
// Override for PW=USD in test/currency.properties is MWP - special
164+
assertEquals("PKZ", Currency.getInstance(Locale.of("", "PK")).getCurrencyCode(),
165+
"Mocked time / module patch not working");
166+
assertEquals("MWP", Currency.getInstance(Locale.of("", "PW")).getCurrencyCode(),
167+
"Mocked time / module patch not working");
168+
// Properly working. Do nothing and move to third invocation
169+
} else {
170+
throw new RuntimeException(
171+
"Incorrect usage of ValidateISO4217. Main method invoked without proper system property value");
172+
}
173+
}
174+
175+
@BeforeAll
176+
static void setUp() throws Exception {
177+
checkUsage();
178+
setUpPatchedClasses();
179+
setUpTestingData();
180+
}
181+
182+
// Enforce correct usage of ValidateISO4217
183+
static void checkUsage() {
184+
if (MOCKED_TIME == null
185+
|| (!MOCKED_TIME.equals("setup") && !MOCKED_TIME.equals("true"))) {
186+
throw new RuntimeException(
187+
"Incorrect usage of ValidateISO4217. Missing \"MOCKED.TIME\" system property");
188+
}
189+
if (MOCKED_TIME.equals("true")) {
190+
checkModulePatchExists();
191+
}
192+
}
193+
194+
static void checkModulePatchExists() {
195+
// Check that the module patch class files exist
196+
for (String className : CLASSES) {
197+
var file = new File(MODULE_PATCH_LOCATION + className);
198+
assertTrue(file.isFile(), "Module patch class files missing");
199+
}
200+
}
201+
202+
// Patch the relevant classes required for module patch
203+
static void setUpPatchedClasses() throws IOException {
204+
if (MOCKED_TIME.equals("setup")) {
205+
new File(MODULE_PATCH_LOCATION).mkdirs();
206+
for (String s : CLASSES) {
207+
patchClass(s);
208+
}
209+
}
210+
}
211+
212+
// Mock calls of System.currentTimeMillis() within Currency to Long.MAX_VALUE.
213+
// This effectively ensures that we are always past any cut-over dates.
214+
private static void patchClass(String name) throws IOException {
215+
CodeTransform codeTransform = (codeBuilder, e) -> {
216+
switch (e) {
217+
case InvokeInstruction i when
218+
i.owner().asInternalName().equals("java/lang/System")
219+
&& i.name().equalsString("currentTimeMillis") ->
220+
codeBuilder.loadConstant(Long.MAX_VALUE); // mock
221+
default -> codeBuilder.accept(e);
222+
}
223+
};
224+
MethodTransform methodTransform = MethodTransform.transformingCode(codeTransform);
225+
ClassTransform classTransform = ClassTransform.transformingMethods(methodTransform);
226+
ClassFile cf = ClassFile.of();
227+
byte[] newBytes = cf.transformClass(cf.parse(
228+
Files.readAllBytes(Paths.get(URI.create("jrt:/java.base/java/util/" + name)))), classTransform);
229+
230+
String patchedClass = MODULE_PATCH_LOCATION + name;
231+
var file = new File(patchedClass);
232+
try (FileOutputStream fos = new FileOutputStream(file)) {
233+
fos.write(newBytes);
234+
}
235+
}
116236

117237
// Sets up the following test data:
118238
// ISO4217Codes, additionalCodes, testCurrencies, codes
119-
@BeforeAll
120239
static void setUpTestingData() throws Exception {
121240
// These functions laterally setup 'testCurrencies' and 'codes'
122241
// at the same time
@@ -169,8 +288,8 @@ private static void processColumns(StringTokenizer tokens, String country) throw
169288
if (format == null) {
170289
createDateFormat();
171290
}
172-
// If the cut-over already passed, use the new curency for ISO4217Codes
173-
if (format.parse(tokens.nextToken()).getTime() < System.currentTimeMillis()) {
291+
// If the cut-over already passed, use the new currency for ISO4217Codes
292+
if (format.parse(tokens.nextToken()).getTime() < currentTimeMillis()) {
174293
currency = tokens.nextToken();
175294
numeric = tokens.nextToken();
176295
minorUnit = tokens.nextToken();
@@ -320,4 +439,10 @@ private static String getSetDiffs(Set<Currency> jreCurrencies, Set<Currency> tes
320439
bldr.append("\n");
321440
return bldr.toString();
322441
}
442+
443+
// Either the current system time, or a mocked value equal to Long.MAX_VALUE
444+
static long currentTimeMillis() {
445+
var mocked = MOCKED_TIME.equals("true");
446+
return mocked ? Long.MAX_VALUE : System.currentTimeMillis();
447+
}
323448
}

test/jdk/java/util/Currency/currency.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ MD=MDD,555,7
1313
ME=MEE,555,8
1414
MF=MFF,555,9
1515
NO=EUR ,978 ,2, 2099-01-01T00:00:00
16+
PK=PKZ,999,0,3000-01-01T00:00:00
17+
PW=MWP,999,0,5000-01-01T00:00:00
1618
SB=EUR,111,2, 2099-01-01T00:00:00
1719
US=euR,978,2,2001-01-01T00:00:00
1820
ZZ\t=\tZZZ\t,\t999\t,\t3

0 commit comments

Comments
 (0)