mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 03:58:21 +00:00
597 lines
25 KiB
Java
597 lines
25 KiB
Java
/*
|
|
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
/**
|
|
* @test
|
|
* @bug 8367530
|
|
* @summary Check enhanced exhaustiveness errors
|
|
* @library /tools/lib
|
|
* @modules jdk.compiler/com.sun.tools.javac.api
|
|
* jdk.compiler/com.sun.tools.javac.main
|
|
* jdk.compiler/com.sun.tools.javac.util
|
|
* @build toolbox.ToolBox toolbox.JavacTask
|
|
* @run main ExhaustivenessConvenientErrors
|
|
*/
|
|
|
|
import com.sun.tools.javac.api.ClientCodeWrapper.DiagnosticSourceUnwrapper;
|
|
import com.sun.tools.javac.util.JCDiagnostic;
|
|
import java.io.IOException;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.Paths;
|
|
import java.util.Collection;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
import toolbox.JavacTask;
|
|
import toolbox.Task;
|
|
import toolbox.TestRunner;
|
|
import toolbox.ToolBox;
|
|
|
|
public class ExhaustivenessConvenientErrors extends TestRunner {
|
|
|
|
ToolBox tb;
|
|
|
|
public static void main(String... args) throws Exception {
|
|
new ExhaustivenessConvenientErrors().runTests();
|
|
}
|
|
|
|
ExhaustivenessConvenientErrors() {
|
|
super(System.err);
|
|
tb = new ToolBox();
|
|
}
|
|
|
|
public void runTests() throws Exception {
|
|
runTests(m -> new Object[] { Paths.get(m.getName()) });
|
|
}
|
|
|
|
@Test
|
|
public void testExhaustiveSealedClasses(Path base) throws Exception {
|
|
doTest(base,
|
|
new String[]{"""
|
|
package lib;
|
|
public sealed interface S permits A, B {}
|
|
""",
|
|
"""
|
|
package lib;
|
|
public final class A implements S {}
|
|
""",
|
|
"""
|
|
package lib;
|
|
public final class B implements S {}
|
|
"""},
|
|
"""
|
|
package test;
|
|
import lib.*;
|
|
public class Test {
|
|
private int test(S obj) {
|
|
return switch (obj) {
|
|
case A a -> 0;
|
|
};
|
|
}
|
|
}
|
|
""",
|
|
"lib.B _");
|
|
}
|
|
|
|
@Test
|
|
public void testExhaustiveSealedClassesTransitive(Path base) throws Exception {
|
|
doTest(base,
|
|
new String[]{"""
|
|
package lib;
|
|
public sealed interface S1 permits S2, A {}
|
|
""",
|
|
"""
|
|
package lib;
|
|
public sealed interface S2 extends S1 permits S3, B {}
|
|
""",
|
|
"""
|
|
package lib;
|
|
public sealed interface S3 extends S2 permits C, D {}
|
|
""",
|
|
"""
|
|
package lib;
|
|
public final class A implements S1 {}
|
|
""",
|
|
"""
|
|
package lib;
|
|
public final class B implements S2 {}
|
|
""",
|
|
"""
|
|
package lib;
|
|
public final class C implements S3 {}
|
|
""",
|
|
"""
|
|
package lib;
|
|
public final class D implements S3 {}
|
|
"""},
|
|
"""
|
|
package test;
|
|
import lib.*;
|
|
public class Test {
|
|
private int test(S1 obj) {
|
|
return switch (obj) {
|
|
case A a -> 0;
|
|
case B a -> 0;
|
|
case D a -> 0;
|
|
};
|
|
}
|
|
}
|
|
""",
|
|
"lib.C _");
|
|
}
|
|
|
|
@Test
|
|
public void testTrivialRecord(Path base) throws Exception {
|
|
doTest(base,
|
|
new String[]{"""
|
|
package lib;
|
|
public sealed interface S permits A, B {}
|
|
""",
|
|
"""
|
|
package lib;
|
|
public final class A implements S {}
|
|
""",
|
|
"""
|
|
package lib;
|
|
public final class B implements S {}
|
|
""",
|
|
"""
|
|
package lib;
|
|
public record R(S s) {}
|
|
"""},
|
|
"""
|
|
package test;
|
|
import lib.*;
|
|
public class Test {
|
|
private int test(R r) {
|
|
return switch (r) {
|
|
case R(A a) -> 0;
|
|
};
|
|
}
|
|
}
|
|
""",
|
|
"lib.R(lib.B _)");
|
|
}
|
|
|
|
@Test
|
|
public void testNonNestedRecord(Path base) throws Exception {
|
|
doTest(base,
|
|
new String[]{"""
|
|
package lib;
|
|
public sealed interface S permits A, B {}
|
|
""",
|
|
"""
|
|
package lib;
|
|
public final class A implements S {}
|
|
""",
|
|
"""
|
|
package lib;
|
|
public final class B implements S {}
|
|
""",
|
|
"""
|
|
package lib;
|
|
public record R(S s1, S s2) {}
|
|
"""},
|
|
"""
|
|
package test;
|
|
import lib.*;
|
|
public class Test {
|
|
private int test(R r) {
|
|
return switch (r) {
|
|
case R(A a, B b) -> 0;
|
|
case R(B b, A a) -> 0;
|
|
};
|
|
}
|
|
}
|
|
""",
|
|
"lib.R(lib.A _, lib.A _)",
|
|
"lib.R(lib.B _, lib.B _)");
|
|
}
|
|
|
|
@Test
|
|
public void testComplex1(Path base) throws Exception {
|
|
doTest(base,
|
|
new String[0],
|
|
"""
|
|
package test;
|
|
import lib.*;
|
|
public class Test {
|
|
private int test(Root r) {
|
|
return switch (r) {
|
|
case Root(R1 _, _, _) -> 0;
|
|
};
|
|
}
|
|
sealed interface Base {}
|
|
record R1() implements Base {}
|
|
record R2() implements Base {}
|
|
record R3(Base b1, Base b2) implements Base {}
|
|
record Root(Base b1, Base b2, Base b3) {}
|
|
}
|
|
""",
|
|
"test.Test.Root(test.Test.R2 _, test.Test.Base _, test.Test.Base _)",
|
|
"test.Test.Root(test.Test.R3 _, test.Test.Base _, test.Test.Base _)");
|
|
}
|
|
|
|
@Test
|
|
public void testComplex2(Path base) throws Exception {
|
|
doTest(base,
|
|
new String[0],
|
|
"""
|
|
package test;
|
|
import lib.*;
|
|
public class Test {
|
|
private int test(Root r) {
|
|
return switch (r) {
|
|
case Root(R1 _, _, _) -> 0;
|
|
case Root(R2 _, R1 _, _) -> 0;
|
|
case Root(R2 _, R2 _, R1 _) -> 0;
|
|
case Root(R2 _, R2(R1 _, R1 _), R2(R1 _, R1 _)) -> 0;
|
|
case Root(R2 _, R2(R1 _, R1 _), R2(R1 _, R2 _)) -> 0;
|
|
case Root(R2 _, R2(R1 _, R1 _), R2(R2 _, R1 _)) -> 0;
|
|
case Root(R2 _, R2(R1 _, R1 _), R2(R2 _, R2 _)) -> 0;
|
|
case Root(R2 _, R2(R1 _, R2 _), R2(R1 _, R1 _)) -> 0;
|
|
case Root(R2 _, R2(R1 _, R2 _), R2(R1 _, R2 _)) -> 0;
|
|
case Root(R2 _, R2(R1 _, R2 _), R2(R2 _, R1 _)) -> 0;
|
|
case Root(R2 _, R2(R1 _, R2 _), R2(R2 _, R2 _)) -> 0;
|
|
case Root(R2 _, R2(R2 _, R1 _), R2(R1 _, R1 _)) -> 0;
|
|
case Root(R2 _, R2(R2 _, R1 _), R2(R1 _, R2 _)) -> 0;
|
|
case Root(R2 _, R2(R2 _, R1 _), R2(R2 _, R1 _)) -> 0;
|
|
case Root(R2 _, R2(R2 _, R1 _), R2(R2 _, R2 _)) -> 0;
|
|
case Root(R2 _, R2(R2 _, R2 _), R2(R1 _, R1 _)) -> 0;
|
|
case Root(R2 _, R2(R2 _, R2 _), R2(R1 _, R2 _)) -> 0;
|
|
case Root(R2 _, R2(R2 _, R2 _), R2(R2 _, R1 _)) -> 0;
|
|
// case Root(R2 _, R2(R2 _, R2 _), R2(R2 _, R2 _)) -> 0;
|
|
};
|
|
}
|
|
sealed interface Base {}
|
|
record R1() implements Base {}
|
|
record R2(Base b1, Base b2) implements Base {}
|
|
record Root(Base b1, Base b2, Base b3) {}
|
|
}
|
|
""",
|
|
"test.Test.Root(test.Test.R2 _, test.Test.R2(test.Test.R2 _, test.Test.R2 _), test.Test.R2(test.Test.R2 _, test.Test.R2 _))");
|
|
}
|
|
|
|
@Test
|
|
public void testComplex3(Path base) throws Exception {
|
|
doTest(base,
|
|
new String[0],
|
|
"""
|
|
package test;
|
|
public class Test {
|
|
private int test(Triple p) {
|
|
return switch (p) {
|
|
case Triple(B _, _, _) -> 0;
|
|
case Triple(_, A _, _) -> 0;
|
|
case Triple(_, _, A _) -> 0;
|
|
case Triple(A p, C(Nested _, NestedBaseA _), _) -> 0;
|
|
case Triple(A p, C(Nested _, NestedBaseB _), C(Nested _, NestedBaseA _)) -> 0;
|
|
case Triple(A p, C(Nested _, NestedBaseB _), C(Nested _, NestedBaseB _)) -> 0;
|
|
case Triple(A p, C(Nested _, NestedBaseB _), C(Nested _, NestedBaseC _)) -> 0;
|
|
case Triple(A p, C(Nested _, NestedBaseC _), C(Nested _, NestedBaseA _)) -> 0;
|
|
case Triple(A p, C(Nested _, NestedBaseC _), C(Nested _, NestedBaseB _)) -> 0;
|
|
// case Path(A p, C(Nested _, NestedBaseC _), C(Nested _, NestedBaseC _)) -> 0;
|
|
};
|
|
}
|
|
record Triple(Base c1, Base c2, Base c3) {}
|
|
sealed interface Base permits A, B {}
|
|
record A(boolean key) implements Base {
|
|
}
|
|
sealed interface B extends Base {}
|
|
record C(Nested n, NestedBase b) implements B {}
|
|
record Nested() {}
|
|
sealed interface NestedBase {}
|
|
record NestedBaseA() implements NestedBase {}
|
|
record NestedBaseB() implements NestedBase {}
|
|
record NestedBaseC() implements NestedBase {}
|
|
}
|
|
""",
|
|
"test.Test.Triple(test.Test.A _, test.Test.C(test.Test.Nested _, test.Test.NestedBaseC _), test.Test.C(test.Test.Nested _, test.Test.NestedBaseC _))");
|
|
}
|
|
|
|
@Test
|
|
public void testComplex4(Path base) throws Exception {
|
|
doTest(base,
|
|
new String[0],
|
|
"""
|
|
package test;
|
|
import lib.*;
|
|
public class Test {
|
|
private int test(Root r) {
|
|
return switch (r) {
|
|
case Root(R1 _, _, _) -> 0;
|
|
case Root(R2 _, R1 _, _) -> 0;
|
|
case Root(R2 _, R2 _, R1 _) -> 0;
|
|
case Root(R2 _, R2(R1 _, R1 _), R2(R1 _, R1 _)) -> 0;
|
|
case Root(R2 _, R2(R1 _, R1 _), R2(R1 _, R2 _)) -> 0;
|
|
case Root(R2 _, R2(R1 _, R1 _), R2(R2 _, R1 _)) -> 0;
|
|
case Root(R2 _, R2(R1 _, R1 _), R2(R2 _, R2 _)) -> 0;
|
|
case Root(R2 _, R2(R1 _, R2 _), R2(R1 _, R1 _)) -> 0;
|
|
case Root(R2 _, R2(R1 _, R2 _), R2(R1 _, R2 _)) -> 0;
|
|
// case Root(R2 _, R2(R1 _, R2 _), R2(R2 _, R1 _)) -> 0;
|
|
case Root(R2 _, R2(R1 _, R2 _), R2(R2 _, R2 _)) -> 0;
|
|
case Root(R2 _, R2(R2 _, R1 _), R2(R1 _, R1 _)) -> 0;
|
|
case Root(R2 _, R2(R2 _, R1 _), R2(R1 _, R2 _)) -> 0;
|
|
case Root(R2 _, R2(R2 _, R1 _), R2(R2 _, R1 _)) -> 0;
|
|
case Root(R2 _, R2(R2 _, R1 _), R2(R2 _, R2 _)) -> 0;
|
|
case Root(R2 _, R2(R2 _, R2 _), R2(R1 _, R1 _)) -> 0;
|
|
case Root(R2 _, R2(R2 _, R2 _), R2(R1 _, R2 _)) -> 0;
|
|
case Root(R2 _, R2(R2 _, R2 _), R2(R2 _, R1 _)) -> 0;
|
|
// case Root(R2 _, R2(R2 _, R2 _), R2(R2 _, R2 _)) -> 0;
|
|
};
|
|
}
|
|
sealed interface Base {}
|
|
record R1() implements Base {}
|
|
record R2(Base b1, Base b2) implements Base {}
|
|
record Root(Base b1, Base b2, Base b3) {}
|
|
}
|
|
""",
|
|
"test.Test.Root(test.Test.R2 _, test.Test.R2(test.Test.Base _, test.Test.R2 _), test.Test.R2(test.Test.R2 _, test.Test.Base _))");
|
|
//ideally, the result would be as follow, but it is difficult to split Base on two distinct places:
|
|
// "test.Test.Root(test.Test.R2 _, test.Test.R2(test.Test.R1 _, test.Test.R2 _), test.Test.R2(test.Test.R2 _, test.Test.R1 _))",
|
|
// "test.Test.Root(test.Test.R2 _, test.Test.R2(test.Test.R2 _, test.Test.R2 _), test.Test.R2(test.Test.R2 _, test.Test.R2 _))");
|
|
}
|
|
|
|
@Test
|
|
public void testComplex5(Path base) throws Exception {
|
|
doTest(base,
|
|
new String[0],
|
|
"""
|
|
package test;
|
|
public class Test {
|
|
private int test(Triple p) {
|
|
return switch (p) {
|
|
case Triple(B _, _, _) -> 0;
|
|
case Triple(_, A _, _) -> 0;
|
|
case Triple(_, _, A _) -> 0;
|
|
// case Triple(A _, C(Nested _, NestedBaseA _), _) -> 0;
|
|
case Triple(A _, C(Nested _, NestedBaseB _), C(Nested _, NestedBaseA _)) -> 0;
|
|
case Triple(A _, C(Nested _, NestedBaseB _), C(Nested _, NestedBaseB _)) -> 0;
|
|
case Triple(A _, C(Nested _, NestedBaseB _), C(Nested _, NestedBaseC _)) -> 0;
|
|
case Triple(A _, C(Nested _, NestedBaseC _), C(Nested _, NestedBaseA _)) -> 0;
|
|
case Triple(A _, C(Nested _, NestedBaseC _), C(Nested _, NestedBaseB _)) -> 0;
|
|
// case Path(A _, C(Nested _, NestedBaseC _), C(Nested _, NestedBaseC _)) -> 0;
|
|
};
|
|
}
|
|
record Triple(Base c1, Base c2, Base c3) {}
|
|
sealed interface Base permits A, B {}
|
|
record A(boolean key) implements Base {
|
|
}
|
|
sealed interface B extends Base {}
|
|
record C(Nested n, NestedBase b) implements B {}
|
|
record Nested() {}
|
|
sealed interface NestedBase {}
|
|
record NestedBaseA() implements NestedBase {}
|
|
record NestedBaseB() implements NestedBase {}
|
|
record NestedBaseC() implements NestedBase {}
|
|
}
|
|
""",
|
|
"test.Test.Triple(test.Test.A _, test.Test.C(test.Test.Nested _, test.Test.NestedBaseA _), test.Test.C _)",
|
|
//the following could be:
|
|
//test.Test.Triple(test.Test.A _, test.Test.C(test.Test.Nested _, test.Test.NestedBaseC _), test.Test.C(test.Test.Nested _, test.Test.NestedBaseC _))
|
|
"test.Test.Triple(test.Test.A _, test.Test.C(test.Test.Nested _, test.Test.NestedBaseC _), test.Test.C _)");
|
|
}
|
|
|
|
@Test
|
|
public void testNoInfiniteRecursion(Path base) throws Exception {
|
|
doTest(base,
|
|
new String[0],
|
|
"""
|
|
package test;
|
|
public class Test {
|
|
private int test(R r) {
|
|
return switch (r) {
|
|
case R(_, _, R(_, _, _, _), String s) -> 0;
|
|
case R(_, _, R(_, _, _, String str), _) -> 0;
|
|
};
|
|
}
|
|
}
|
|
public record R(R r1, R r2, R r3, Object o) {}
|
|
""",
|
|
"test.R(test.R _, test.R _, test.R(test.R _, test.R _, test.R _, java.lang.Object _), java.lang.Object _)");
|
|
}
|
|
|
|
@Test
|
|
public void testEnum(Path base) throws Exception {
|
|
doTest(base,
|
|
new String[0],
|
|
"""
|
|
package test;
|
|
public class Test {
|
|
private int test(I i) {
|
|
return switch (i) {
|
|
case E.A -> 0;
|
|
case C _ -> 1;
|
|
};
|
|
}
|
|
sealed interface I {}
|
|
enum E implements I {A, B}
|
|
final class C implements I {}
|
|
}
|
|
public record R(R r1, R r2, R r3, Object o) {}
|
|
""",
|
|
"test.Test.E.B");
|
|
doTest(base,
|
|
new String[0],
|
|
"""
|
|
package test;
|
|
public class Test {
|
|
private int test(I i) {
|
|
return switch (i) {
|
|
case C _ -> 1;
|
|
};
|
|
}
|
|
sealed interface I {}
|
|
enum E implements I {A, B}
|
|
final class C implements I {}
|
|
}
|
|
public record R(R r1, R r2, R r3, Object o) {}
|
|
""",
|
|
"test.Test.E _");
|
|
}
|
|
|
|
@Test
|
|
public void testInstantiateComponentTypes(Path base) throws Exception {
|
|
doTest(base,
|
|
new String[0],
|
|
"""
|
|
package test;
|
|
public class Test {
|
|
private int test(Pair<Base<Base>> p) {
|
|
return switch (p) {
|
|
case Pair(A(A(_)) -> 0;
|
|
case Pair(A(B(_)) -> 0;
|
|
case Pair(B(A(_)) -> 0;
|
|
};
|
|
}
|
|
record Pair<T>(T c) {}
|
|
sealed interface Base<T> permits A, B {}
|
|
record A<T>(T c) implements Base<T> {}
|
|
record B<T>(T c) implements Base<T> {}
|
|
}
|
|
""",
|
|
"test.Test.Pair(test.Test.B(test.Test.B _))");
|
|
}
|
|
|
|
@Test
|
|
public void testNeedToExpandIfRecordExists(Path base) throws Exception {
|
|
doTest(base,
|
|
new String[0],
|
|
"""
|
|
package test;
|
|
class Test {
|
|
sealed interface A { }
|
|
record B() implements A { }
|
|
record C(A a) implements A { }
|
|
|
|
void test(A a) {
|
|
switch (a) {
|
|
case C(B _) -> throw null;
|
|
}
|
|
}
|
|
} """,
|
|
"test.Test.B _",
|
|
"test.Test.C(test.Test.C _)");
|
|
}
|
|
|
|
@Test
|
|
public void testComplex6(Path base) throws Exception {
|
|
doTest(base,
|
|
new String[0],
|
|
"""
|
|
public class Test {
|
|
sealed interface Base {}
|
|
record NoOp() implements Base {}
|
|
record Const() implements Base {}
|
|
record Pair(Base n1,
|
|
Base b2) implements Base {}
|
|
|
|
int t(Base b) {
|
|
return switch (b) {
|
|
case NoOp _ -> 0;
|
|
case Const _ -> 0;
|
|
case Pair(NoOp _, _) -> 0;
|
|
case Pair(Const _, _) -> 0;
|
|
case Pair(Pair _, NoOp _) -> 0;
|
|
case Pair(Pair _, Const _) -> 0;
|
|
case Pair(Pair _, Pair(NoOp _, _)) -> 0;
|
|
case Pair(Pair _, Pair(Const _, _)) -> 0;
|
|
case Pair(Pair _, Pair(Pair(NoOp _, _), _)) -> 0;
|
|
case Pair(Pair _, Pair(Pair(Const _, _), _)) -> 0;
|
|
case Pair(Pair(NoOp _, _), Pair(Pair(Pair _, _), _)) -> 0;
|
|
case Pair(Pair(Const _, _), Pair(Pair(Pair _, _), _)) -> 0;
|
|
// case Pair(Pair(Pair _, _), Pair(Pair(Pair _, _), _)) -> 0;
|
|
};
|
|
}
|
|
}
|
|
""",
|
|
"Test.Pair(Test.Pair(Test.Pair _, Test.Base _), Test.Pair(Test.Pair(Test.Pair _, Test.Base _), Test.Base _))");
|
|
}
|
|
|
|
private void doTest(Path base, String[] libraryCode, String testCode, String... expectedMissingPatterns) throws IOException {
|
|
Path current = base.resolve(".");
|
|
Path libClasses = current.resolve("libClasses");
|
|
|
|
Files.createDirectories(libClasses);
|
|
|
|
if (libraryCode.length != 0) {
|
|
Path libSrc = current.resolve("lib-src");
|
|
|
|
for (String code : libraryCode) {
|
|
tb.writeJavaFiles(libSrc, code);
|
|
}
|
|
|
|
new JavacTask(tb)
|
|
.outdir(libClasses)
|
|
.files(tb.findJavaFiles(libSrc))
|
|
.run();
|
|
}
|
|
|
|
Path src = current.resolve("src");
|
|
tb.writeJavaFiles(src, testCode);
|
|
|
|
Path classes = current.resolve("libClasses");
|
|
|
|
Files.createDirectories(libClasses);
|
|
Set<String> missingPatterns = new HashSet<>();
|
|
|
|
new JavacTask(tb)
|
|
.options("-XDrawDiagnostics",
|
|
"-XDdev",
|
|
"-Xlint:-preview",
|
|
"--class-path", libClasses.toString(),
|
|
"-XDshould-stop.at=FLOW",
|
|
"-XDshould-stop.ifNoError=FLOW",
|
|
"-XDexhaustivityMaxBaseChecks=" + Long.MAX_VALUE) //never give up
|
|
.outdir(classes)
|
|
.files(tb.findJavaFiles(src))
|
|
.diagnosticListener(d -> {
|
|
if ("compiler.err.not.exhaustive.details".equals(d.getCode()) ||
|
|
"compiler.err.not.exhaustive.statement.details".equals(d.getCode())) {
|
|
if (d instanceof DiagnosticSourceUnwrapper uw) {
|
|
d = uw.d;
|
|
}
|
|
if (d instanceof JCDiagnostic.MultilineDiagnostic diag) {
|
|
diag.getSubdiagnostics()
|
|
.stream()
|
|
.map(fragment -> fragment.getArgs()[0].toString())
|
|
.forEach(missingPatterns::add);
|
|
}
|
|
}
|
|
})
|
|
.run(Task.Expect.FAIL)
|
|
.writeAll()
|
|
.getOutputLines(Task.OutputKind.DIRECT);
|
|
|
|
Set<String> expectedPatterns = new HashSet<>(List.of(expectedMissingPatterns));
|
|
|
|
if (!expectedPatterns.equals(missingPatterns)) {
|
|
throw new AssertionError("Incorrect errors, expected: " + expectedPatterns +
|
|
", actual: " + missingPatterns);
|
|
}
|
|
}
|
|
|
|
}
|