Skip to content

Commit f75692d

Browse files
author
Alexander Matveev
committed
8383821: IllegalStateException when command executed by jpackage "Executor" timeouts
Reviewed-by: asemenyuk
1 parent 7da2477 commit f75692d

4 files changed

Lines changed: 59 additions & 10 deletions

File tree

src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/Codesign.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,19 @@
3434
import java.util.Optional;
3535
import java.util.function.Consumer;
3636
import java.util.function.Supplier;
37-
import jdk.jpackage.internal.util.CommandOutputControl.UnexpectedExitCodeException;
37+
import jdk.jpackage.internal.util.CommandOutputControl.UnexpectedResultException;
3838

3939
public final class Codesign {
4040

4141
public static final class CodesignException extends Exception {
4242

43-
CodesignException(UnexpectedExitCodeException cause) {
43+
CodesignException(UnexpectedResultException cause) {
4444
super(Objects.requireNonNull(cause));
4545
}
4646

4747
@Override
48-
public UnexpectedExitCodeException getCause() {
49-
return (UnexpectedExitCodeException)super.getCause();
48+
public UnexpectedResultException getCause() {
49+
return (UnexpectedResultException)super.getCause();
5050
}
5151

5252
private static final long serialVersionUID = 1L;
@@ -97,7 +97,7 @@ public void applyTo(Path path) throws IOException, CodesignException {
9797

9898
try {
9999
exec.execute().expectExitCode(0);
100-
} catch (UnexpectedExitCodeException ex) {
100+
} catch (UnexpectedResultException ex) {
101101
throw new CodesignException(ex);
102102
}
103103
}

src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/CommandOutputControl.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -913,18 +913,20 @@ public int getExitCode() {
913913
});
914914
}
915915

916-
public Result expectExitCode(int main, int... other) throws UnexpectedExitCodeException {
916+
public Result expectExitCode(int main, int... other) throws UnexpectedResultException {
917917
return expectExitCode(v -> {
918918
return IntStream.concat(IntStream.of(main), IntStream.of(other)).boxed().anyMatch(Predicate.isEqual(v));
919919
});
920920
}
921921

922-
public Result expectExitCode(Collection<Integer> expected) throws UnexpectedExitCodeException {
922+
public Result expectExitCode(Collection<Integer> expected) throws UnexpectedResultException {
923923
return expectExitCode(expected::contains);
924924
}
925925

926-
public Result expectExitCode(IntPredicate expected) throws UnexpectedExitCodeException {
927-
if (!expected.test(getExitCode())) {
926+
public Result expectExitCode(IntPredicate expected) throws UnexpectedResultException {
927+
if (!expected.test(exitCode.orElseThrow(() -> {
928+
return new UnavailableExitCodeException(this);
929+
}))) {
928930
throw new UnexpectedExitCodeException(this);
929931
}
930932
return this;
@@ -1089,6 +1091,23 @@ private static Result requireExitCode(Result v) {
10891091
private static final long serialVersionUID = 1L;
10901092
}
10911093

1094+
public static final class UnavailableExitCodeException extends UnexpectedResultException {
1095+
1096+
public UnavailableExitCodeException(Result value, String message) {
1097+
super(value, message);
1098+
if (value.exitCode.isPresent()) {
1099+
throw new IllegalArgumentException();
1100+
}
1101+
}
1102+
1103+
public UnavailableExitCodeException(Result value) {
1104+
this(value, String.format("Exit code unavailable from executing the command %s",
1105+
value.execAttrs().printableCommandLine()));
1106+
}
1107+
1108+
private static final long serialVersionUID = 1L;
1109+
}
1110+
10921111
public String description() {
10931112
var tokens = outputStreamsControl.descriptionTokens();
10941113
if (isBinaryOutput()) {

test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Executor.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import java.util.stream.Stream;
4343
import jdk.jpackage.internal.util.CommandLineFormat;
4444
import jdk.jpackage.internal.util.CommandOutputControl;
45+
import jdk.jpackage.internal.util.CommandOutputControl.UnexpectedResultException;
4546
import jdk.jpackage.internal.util.CommandOutputControl.UnexpectedExitCodeException;
4647
import jdk.jpackage.internal.util.RetryExecutor;
4748
import jdk.jpackage.internal.util.function.ExceptionBox;
@@ -371,7 +372,15 @@ public RetryExecutor<Result, UnexpectedExitCodeException> retryUntilExitCodeIs(
371372
int mainExpectedExitCode, int... otherExpectedExitCodes) {
372373
return new RetryExecutor<Result, UnexpectedExitCodeException>(UnexpectedExitCodeException.class).setExecutable(() -> {
373374
var result = executeWithoutExitCodeCheck();
374-
result.base().expectExitCode(mainExpectedExitCode, otherExpectedExitCodes);
375+
try {
376+
result.base().expectExitCode(mainExpectedExitCode, otherExpectedExitCodes);
377+
} catch (UnexpectedResultException ex) {
378+
if (ex instanceof UnexpectedExitCodeException uecex) {
379+
throw uecex; // Pass to exception mapper
380+
}
381+
// Unreachable, because the result must always have the exit code, as the executor never runs commands with a timeout.
382+
throw ExceptionBox.reachedUnreachable();
383+
}
375384
return result;
376385
}).setExceptionMapper((UnexpectedExitCodeException ex) -> {
377386
createResult(ex.getResult()).assertExitCodeIs(mainExpectedExitCode, otherExpectedExitCodes);

test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/CommandOutputControlTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,20 @@ public void test_Result_expectExitCode_negative(boolean collection) {
261261
assertEquals("Unexpected exit code 3 from executing the command <unknown>", ex.getMessage());
262262
}
263263

264+
@Test
265+
public void test_Result_expectExitCode_unavailable() {
266+
var result = CommandOutputControl.Result.build().noExitCode().create();
267+
268+
var ex = assertThrowsExactly(CommandOutputControl.UnavailableExitCodeException.class, () -> {
269+
result.expectExitCode(0);
270+
});
271+
272+
assertNull(ex.getCause());
273+
assertSame(result, ex.getResult());
274+
assertEquals(String.format("Exit code unavailable from executing the command %s",
275+
result.execAttrs().printableCommandLine()), ex.getMessage());
276+
}
277+
264278
@ParameterizedTest
265279
@MethodSource
266280
public void test_Result_toCharacterResult(ToCharacterResultTestSpec spec) throws IOException, InterruptedException {
@@ -352,6 +366,13 @@ public void test_timeout_expires(ExecutableType mode) throws InterruptedExceptio
352366
var getExitCodeEx = assertThrowsExactly(IllegalStateException.class, result::getExitCode);
353367
assertEquals(("Exit code is unavailable for timed-out command"), getExitCodeEx.getMessage());
354368

369+
// Verify UnavailableExitCodeException
370+
var expectExitCodeEx = assertThrowsExactly(CommandOutputControl.UnavailableExitCodeException.class, () -> {
371+
result.expectExitCode(0);
372+
});
373+
assertEquals(String.format("Exit code unavailable from executing the command %s",
374+
result.execAttrs().printableCommandLine()), expectExitCodeEx.getMessage());
375+
355376
// We want to check that the saved output contains only the text emitted before the "sleep" action.
356377
// It works for a subprocess, but in the case of a ToolProvider, sometimes the timing is such
357378
// that it gets interrupted before having written anything to the stdout, and the saved output is empty.

0 commit comments

Comments
 (0)