Skip to content

Commit b3be7ff

Browse files
Add test to run multiple instances of jpackage tool provider asynchronously
1 parent 77d01a9 commit b3be7ff

File tree

1 file changed

+210
-0
lines changed

1 file changed

+210
-0
lines changed
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*
2+
* Copyright (c) 2025, 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+
import java.io.ByteArrayOutputStream;
25+
import java.io.PrintStream;
26+
import java.io.PrintWriter;
27+
import java.nio.charset.StandardCharsets;
28+
import java.util.Collection;
29+
import java.util.List;
30+
import java.util.Objects;
31+
import java.util.Optional;
32+
import java.util.concurrent.Callable;
33+
import java.util.concurrent.Executors;
34+
import java.util.concurrent.TimeUnit;
35+
import java.util.function.Predicate;
36+
import java.util.spi.ToolProvider;
37+
import java.util.stream.IntStream;
38+
import jdk.jpackage.internal.util.function.ThrowingRunnable;
39+
import jdk.jpackage.test.Annotations.ParameterSupplier;
40+
import jdk.jpackage.test.Annotations.Test;
41+
import jdk.jpackage.test.JPackageCommand;
42+
import jdk.jpackage.test.JavaTool;
43+
import jdk.jpackage.test.Main;
44+
import jdk.jpackage.test.PackageTest;
45+
import jdk.jpackage.test.RunnablePackageTest.Action;
46+
import jdk.jpackage.test.TKit;
47+
48+
/*
49+
* @test
50+
* @summary Runs multiple jpackage tool provider instances asynchronously
51+
* @library /test/jdk/tools/jpackage/helpers
52+
* @build jdk.jpackage.test.*
53+
* @compile -Xlint:all -Werror AsyncTest.java
54+
* @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
55+
* --jpt-run=AsyncTest
56+
*/
57+
public class AsyncTest {
58+
59+
@Test
60+
public void test() throws Throwable {
61+
//
62+
// Run test cases from InternalAsyncTest class asynchronously.
63+
// Spawn a thread for every test case.
64+
// Input data for test cases will be cooked asynchronously but in a safe way because every test case has an isolated work directory.
65+
// Multiple jpackage tool provider instances will be invoked asynchronously.
66+
//
67+
68+
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
69+
70+
var testFuncNames = List.of("testAppImage", "testNativeBundle");
71+
72+
var runArg = String.format("--jpt-run=%s", InternalAsyncTest.class.getName());
73+
74+
var futures = executor.invokeAll(IntStream.range(0, JOB_COUNT).mapToObj(Integer::toString).<Workload>mapMulti((idx, consumer) -> {
75+
for (var testFuncName : testFuncNames) {
76+
var id = String.format("%s(%s)", testFuncName, idx);
77+
consumer.accept(new Workload(() -> {
78+
Main.main(runArg, String.format("--jpt-include=%s", id));
79+
}, id));
80+
}
81+
}).toList());
82+
83+
// Wait for all test cases completion.
84+
for (var future : futures) {
85+
future.get(3, TimeUnit.MINUTES);
86+
}
87+
88+
Throwable[] fatalError = new Throwable[1];
89+
90+
for (var future : futures) {
91+
var result = future.get();
92+
TKit.trace(String.format("[%s] STDOUT BEGIN\n%s", result.id(), result.stdoutBuffer()));
93+
TKit.trace(String.format("[%s] STDOUT END", result.id()));
94+
TKit.trace(String.format("[%s] STDERR BEGIN\n%s", result.id(), result.stderrBuffer()));
95+
TKit.trace(String.format("[%s] STDERR END", result.id()));
96+
result.throwable().filter(Predicate.not(TKit::isSkippedException)).ifPresent(t -> {
97+
fatalError[0] = t;
98+
});
99+
}
100+
101+
if (fatalError[0] != null) {
102+
throw fatalError[0];
103+
}
104+
}
105+
}
106+
107+
public static final class InternalAsyncTest {
108+
109+
@Test
110+
@ParameterSupplier("ids")
111+
public void testAppImage(int id) throws Exception {
112+
init(JPackageCommand.helloAppImage()).executeAndAssertHelloAppImageCreated();
113+
}
114+
115+
@Test
116+
@ParameterSupplier("ids")
117+
public void testNativeBundle(int id) throws Exception {
118+
new PackageTest().configureHelloApp().addInitializer(cmd -> {
119+
init(cmd);
120+
}).run(Action.CREATE_AND_UNPACK);
121+
}
122+
123+
public static Collection<Object[]> ids() {
124+
return IntStream.range(0, JOB_COUNT).mapToObj(Integer::valueOf).map(v -> {
125+
return new Object[] {v};
126+
}).toList();
127+
}
128+
}
129+
130+
private static JPackageCommand init(JPackageCommand cmd) {
131+
return cmd.useToolProvider(true).setFakeRuntime().setArgumentValue("--name", "Foo");
132+
}
133+
134+
135+
private record Result(String stdoutBuffer, String stderrBuffer, String id, Optional<Throwable> throwable) {
136+
137+
Result {
138+
Objects.requireNonNull(stdoutBuffer);
139+
Objects.requireNonNull(stderrBuffer);
140+
Objects.requireNonNull(id);
141+
Objects.requireNonNull(throwable);
142+
}
143+
}
144+
145+
146+
private record Workload(
147+
ByteArrayOutputStream stdoutBuffer,
148+
ByteArrayOutputStream stderrBuffer,
149+
ThrowingRunnable runnable,
150+
String id) implements Callable<Result> {
151+
152+
Workload {
153+
Objects.requireNonNull(stdoutBuffer);
154+
Objects.requireNonNull(stderrBuffer);
155+
Objects.requireNonNull(runnable);
156+
Objects.requireNonNull(id);
157+
}
158+
159+
Workload(ThrowingRunnable runnable, String id) {
160+
this(new ByteArrayOutputStream(), new ByteArrayOutputStream(), runnable, id);
161+
}
162+
163+
private String stdoutBufferAsString() {
164+
return new String(stdoutBuffer.toByteArray(), StandardCharsets.UTF_8);
165+
}
166+
167+
private String stderrBufferAsString() {
168+
return new String(stderrBuffer.toByteArray(), StandardCharsets.UTF_8);
169+
}
170+
171+
@Override
172+
public Result call() {
173+
// Reset the current test inherited in the state from the parent thread.
174+
TKit.state(DEFAULT_STATE);
175+
176+
var defaultToolProvider = JavaTool.JPACKAGE.asToolProvider();
177+
178+
JPackageCommand.useToolProviderByDefault(new ToolProvider() {
179+
180+
@Override
181+
public int run(PrintWriter out, PrintWriter err, String... args) {
182+
try (var bufOut = new PrintWriter(stdoutBuffer, true, StandardCharsets.UTF_8);
183+
var bufErr = new PrintWriter(stderrBuffer, true, StandardCharsets.UTF_8)) {
184+
return defaultToolProvider.run(bufOut, bufErr, args);
185+
}
186+
}
187+
188+
@Override
189+
public String name() {
190+
return defaultToolProvider.name();
191+
}
192+
});
193+
194+
Optional<Throwable> err = Optional.empty();
195+
try (var bufOut = new PrintStream(stdoutBuffer, true, StandardCharsets.UTF_8);
196+
var bufErr = new PrintStream(stderrBuffer, true, StandardCharsets.UTF_8)) {
197+
TKit.withStackTraceStream(() -> {
198+
TKit.withMainLogStream(runnable, bufOut);
199+
}, bufErr);
200+
} catch (Throwable t) {
201+
err = Optional.of(t);
202+
}
203+
return new Result(stdoutBufferAsString(), stderrBufferAsString(), id, err);
204+
}
205+
}
206+
207+
208+
private static final int JOB_COUNT = 10;
209+
private static final TKit.State DEFAULT_STATE = TKit.state();
210+
}

0 commit comments

Comments
 (0)