S8 apply Palantir formatting to maintained Java sources
All checks were successful
Build, test and publish / verify (push) Successful in 1m39s
All checks were successful
Build, test and publish / verify (push) Successful in 1m39s
This commit is contained in:
296
ci/CiTests.java
296
ci/CiTests.java
@@ -6,26 +6,41 @@ import java.util.*;
|
||||
class CiTests {
|
||||
static final String SHA = "a".repeat(40);
|
||||
static int count;
|
||||
interface Test { void run() throws Exception; }
|
||||
|
||||
interface Test {
|
||||
void run() throws Exception;
|
||||
}
|
||||
|
||||
static void test(String name, Test test) throws Exception {
|
||||
test.run(); count++; System.out.println("PASS " + name);
|
||||
test.run();
|
||||
count++;
|
||||
System.out.println("PASS " + name);
|
||||
}
|
||||
|
||||
static void equal(Object expected, Object actual) {
|
||||
if (!Objects.equals(expected,actual)) throw new AssertionError("Expected " + expected + ", got " + actual);
|
||||
if (!Objects.equals(expected, actual)) throw new AssertionError("Expected " + expected + ", got " + actual);
|
||||
}
|
||||
|
||||
static void failure(Test action) throws Exception {
|
||||
try { action.run(); } catch (Exception expected) { return; }
|
||||
try {
|
||||
action.run();
|
||||
} catch (Exception expected) {
|
||||
return;
|
||||
}
|
||||
throw new AssertionError("Expected failure");
|
||||
}
|
||||
|
||||
static void run() throws Exception {
|
||||
count = 0;
|
||||
test("JSON preserves configuration values and escaped text", () -> {
|
||||
String json = "{\"text\":\"line\\nquote\\\"\\u20ac\",\"values\":[null,true,false,1,-2.5e2],\"nested\":{}}";
|
||||
Object data = Json.parse(json); equal(data,Json.parse(Json.write(data)));
|
||||
equal("line\nquote\"€",Json.object(data).get("text"));
|
||||
Object data = Json.parse(json);
|
||||
equal(data, Json.parse(Json.write(data)));
|
||||
equal("line\nquote\"€", Json.object(data).get("text"));
|
||||
});
|
||||
test("JSON rejects malformed/ambiguous input", () -> {
|
||||
for (String value : List.of("{\"a\":null,\"a\":1}","{} []","[1,]","01","\"\\uZZZZ\"","\"line\n\"","truefalse","+1"))
|
||||
for (String value : List.of(
|
||||
"{\"a\":null,\"a\":1}", "{} []", "[1,]", "01", "\"\\uZZZZ\"", "\"line\n\"", "truefalse", "+1"))
|
||||
failure(() -> Json.parse(value));
|
||||
});
|
||||
test("Docker configuration merge is idempotent and preserves backup", () -> {
|
||||
@@ -33,151 +48,288 @@ class CiTests {
|
||||
try {
|
||||
Path file = dir.resolve("daemon.json");
|
||||
String original = "{\"features\":{\"buildkit\":true},\"insecure-registries\":[\"existing:5000\"]}";
|
||||
Files.writeString(file,original);
|
||||
Setup.configureDocker(file); String first = Files.readString(file); Setup.configureDocker(file);
|
||||
equal(first,Files.readString(file));
|
||||
equal(List.of("existing:5000","192.168.2.28:3000"),Json.object(Json.parse(first)).get("insecure-registries"));
|
||||
equal(original,Files.readString(dir.resolve("daemon.json.before-agiledancers")));
|
||||
} finally { Pipeline.deleteTree(dir); }
|
||||
Files.writeString(file, original);
|
||||
Setup.configureDocker(file);
|
||||
String first = Files.readString(file);
|
||||
Setup.configureDocker(file);
|
||||
equal(first, Files.readString(file));
|
||||
equal(
|
||||
List.of("existing:5000", "192.168.2.28:3000"),
|
||||
Json.object(Json.parse(first)).get("insecure-registries"));
|
||||
equal(original, Files.readString(dir.resolve("daemon.json.before-agiledancers")));
|
||||
} finally {
|
||||
Pipeline.deleteTree(dir);
|
||||
}
|
||||
});
|
||||
test("Invalid Docker configuration is unchanged", () -> {
|
||||
Path dir = Files.createTempDirectory("ci-docker-test-");
|
||||
try {
|
||||
Path file = dir.resolve("daemon.json"); String original = "{\"insecure-registries\":\"invalid\"}";
|
||||
Files.writeString(file,original); failure(() -> Setup.configureDocker(file)); equal(original,Files.readString(file));
|
||||
} finally { Pipeline.deleteTree(dir); }
|
||||
Path file = dir.resolve("daemon.json");
|
||||
String original = "{\"insecure-registries\":\"invalid\"}";
|
||||
Files.writeString(file, original);
|
||||
failure(() -> Setup.configureDocker(file));
|
||||
equal(original, Files.readString(file));
|
||||
} finally {
|
||||
Pipeline.deleteTree(dir);
|
||||
}
|
||||
});
|
||||
test("Feature branches never contact registry", () -> {
|
||||
try (Fixture f = new Fixture()) {
|
||||
f.pipeline.env.put("CI_REF","refs/heads/feature/test"); f.pipeline.publish(); equal(0L,f.calls("login")); equal(0L,f.calls("push"));
|
||||
f.pipeline.env.put("CI_REF", "refs/heads/feature/test");
|
||||
f.pipeline.publish();
|
||||
equal(0L, f.calls("login"));
|
||||
equal(0L, f.calls("push"));
|
||||
}
|
||||
});
|
||||
test("Unverified commit cannot publish", () -> {
|
||||
try (Fixture f = new Fixture()) {
|
||||
Files.delete(f.root.resolve("ci/artifacts/verified-commit")); failure(f.pipeline::publish); equal(0L,f.calls("push"));
|
||||
Files.delete(f.root.resolve("ci/artifacts/verified-commit"));
|
||||
failure(f.pipeline::publish);
|
||||
equal(0L, f.calls("push"));
|
||||
}
|
||||
});
|
||||
test("Publication pullback and rerun preserve commit tags", () -> {
|
||||
try (Fixture f = new Fixture()) {
|
||||
f.pipeline.publish(); equal(4L,f.calls("push")); f.pipeline.publish(); equal(6L,f.calls("push")); equal(8L,f.calls("pull"));
|
||||
f.pipeline.publish();
|
||||
equal(4L, f.calls("push"));
|
||||
f.pipeline.publish();
|
||||
equal(6L, f.calls("push"));
|
||||
equal(8L, f.calls("pull"));
|
||||
}
|
||||
});
|
||||
test("Partial publication is completed without overwriting backend", () -> {
|
||||
try (Fixture f = new Fixture()) {
|
||||
f.fake.pushFailure = "frontend:" + SHA; failure(f.pipeline::publish);
|
||||
equal(0L,f.fake.calls.stream().filter(a -> a.contains("push") && a.getLast().endsWith(":main")).count());
|
||||
f.fake.pushFailure = ""; f.pipeline.publish();
|
||||
equal(1L,f.fake.calls.stream().filter(a -> a.contains("push") && a.getLast().endsWith("backend:" + SHA)).count());
|
||||
f.fake.pushFailure = "frontend:" + SHA;
|
||||
failure(f.pipeline::publish);
|
||||
equal(
|
||||
0L,
|
||||
f.fake.calls.stream()
|
||||
.filter(a -> a.contains("push") && a.getLast().endsWith(":main"))
|
||||
.count());
|
||||
f.fake.pushFailure = "";
|
||||
f.pipeline.publish();
|
||||
equal(
|
||||
1L,
|
||||
f.fake.calls.stream()
|
||||
.filter(a -> a.contains("push") && a.getLast().endsWith("backend:" + SHA))
|
||||
.count());
|
||||
}
|
||||
});
|
||||
test("Authentication error is not a missing tag", () -> {
|
||||
try (Fixture f = new Fixture()) { f.fake.manifestError = true; failure(f.pipeline::publish); equal(0L,f.calls("push")); }
|
||||
try (Fixture f = new Fixture()) {
|
||||
f.fake.manifestError = true;
|
||||
failure(f.pipeline::publish);
|
||||
equal(0L, f.calls("push"));
|
||||
}
|
||||
});
|
||||
test("Pull failure blocks moving tags", () -> {
|
||||
try (Fixture f = new Fixture()) {
|
||||
f.fake.pullFailure = "backend:" + SHA; failure(f.pipeline::publish);
|
||||
equal(0L,f.fake.calls.stream().filter(a -> a.contains("push") && a.getLast().endsWith(":main")).count());
|
||||
f.fake.pullFailure = "backend:" + SHA;
|
||||
failure(f.pipeline::publish);
|
||||
equal(
|
||||
0L,
|
||||
f.fake.calls.stream()
|
||||
.filter(a -> a.contains("push") && a.getLast().endsWith(":main"))
|
||||
.count());
|
||||
}
|
||||
});
|
||||
test("Older commit cannot move main", () -> {
|
||||
try (Fixture f = new Fixture()) { f.fake.head = "c".repeat(40); f.pipeline.publish(); equal(2L,f.calls("push")); }
|
||||
try (Fixture f = new Fixture()) {
|
||||
f.fake.head = "c".repeat(40);
|
||||
f.pipeline.publish();
|
||||
equal(2L, f.calls("push"));
|
||||
}
|
||||
});
|
||||
test("Unavailable Docker fails before build with a concrete cause", () -> {
|
||||
try (Fixture f = new Fixture()) {
|
||||
f.fake.dockerAvailable = false;
|
||||
try { f.pipeline.verify(); throw new AssertionError("Expected failure"); }
|
||||
catch (IllegalArgumentException error) { equal(true,error.getMessage().contains("Docker is unavailable")); }
|
||||
equal(0L,f.fake.calls.stream().filter(a -> a.getFirst().equals("mvn")).count());
|
||||
try {
|
||||
f.pipeline.verify();
|
||||
throw new AssertionError("Expected failure");
|
||||
} catch (IllegalArgumentException error) {
|
||||
equal(true, error.getMessage().contains("Docker is unavailable"));
|
||||
}
|
||||
equal(
|
||||
0L,
|
||||
f.fake.calls.stream()
|
||||
.filter(a -> a.getFirst().equals("mvn"))
|
||||
.count());
|
||||
}
|
||||
});
|
||||
test("Architecture mismatch blocks publication", () -> {
|
||||
try (Fixture f = new Fixture()) { f.pipeline.env.put("TARGET_PLATFORM","linux/arm64"); failure(f.pipeline::publish); equal(0L,f.calls("push")); }
|
||||
try (Fixture f = new Fixture()) {
|
||||
f.pipeline.env.put("TARGET_PLATFORM", "linux/arm64");
|
||||
failure(f.pipeline::publish);
|
||||
equal(0L, f.calls("push"));
|
||||
}
|
||||
});
|
||||
test("Failed verification removes success marker and preserves exit status", () -> {
|
||||
try (Fixture f = new Fixture()) {
|
||||
f.fake.mavenFailure = true;
|
||||
try { f.pipeline.verify(); throw new AssertionError("Expected failure"); }
|
||||
catch (Commands.Failure error) { equal(42,error.exitCode); }
|
||||
equal(List.of("mvn", "-B", "-ntp", "-Dformat.mode=check", "clean", "verify"),
|
||||
f.fake.calls.stream().filter(a -> a.getFirst().equals("mvn")).findFirst().orElseThrow());
|
||||
equal(false,Files.exists(f.root.resolve("ci/artifacts/verified-commit")));
|
||||
equal(0L,f.calls("build")); equal(0L,f.calls("push"));
|
||||
try {
|
||||
f.pipeline.verify();
|
||||
throw new AssertionError("Expected failure");
|
||||
} catch (Commands.Failure error) {
|
||||
equal(42, error.exitCode);
|
||||
}
|
||||
equal(
|
||||
List.of("mvn", "-B", "-ntp", "-Dformat.mode=check", "clean", "verify"),
|
||||
f.fake.calls.stream()
|
||||
.filter(a -> a.getFirst().equals("mvn"))
|
||||
.findFirst()
|
||||
.orElseThrow());
|
||||
equal(false, Files.exists(f.root.resolve("ci/artifacts/verified-commit")));
|
||||
equal(0L, f.calls("build"));
|
||||
equal(0L, f.calls("push"));
|
||||
}
|
||||
});
|
||||
test("Frontend lint runs after install and blocks tests and build on failure", () -> {
|
||||
try (Fixture f = new Fixture()) {
|
||||
f.fake.lintFailure = true;
|
||||
try { f.pipeline.verifyFrontend(); throw new AssertionError("Expected failure"); }
|
||||
catch (Commands.Failure error) { equal(1,error.exitCode); }
|
||||
try {
|
||||
f.pipeline.verifyFrontend();
|
||||
throw new AssertionError("Expected failure");
|
||||
} catch (Commands.Failure error) {
|
||||
equal(1, error.exitCode);
|
||||
}
|
||||
equal(List.of(List.of("npm", "ci"), List.of("npm", "run", "lint")), f.fake.calls);
|
||||
}
|
||||
});
|
||||
test("Frontend verification checks before testing and building", () -> {
|
||||
try (Fixture f = new Fixture()) {
|
||||
f.pipeline.verifyFrontend();
|
||||
equal(List.of("ci", "run lint", "test -- --reporters=default --reporters=junit --output-file=unit-results/junit.xml", "run build"),
|
||||
f.fake.calls.stream().map(a -> String.join(" ", a.subList(1,a.size()))).toList());
|
||||
equal(
|
||||
List.of(
|
||||
"ci",
|
||||
"run lint",
|
||||
"test -- --reporters=default --reporters=junit --output-file=unit-results/junit.xml",
|
||||
"run build"),
|
||||
f.fake.calls.stream()
|
||||
.map(a -> String.join(" ", a.subList(1, a.size())))
|
||||
.toList());
|
||||
}
|
||||
});
|
||||
test("Process execution enforces its deadline", () -> {
|
||||
Path dir = Files.createTempDirectory("ci-process-test-");
|
||||
try {
|
||||
Path file = dir.resolve("Sleep.java");
|
||||
Files.writeString(file,"class Sleep { public static void main(String[] args) throws Exception { Thread.sleep(10000); } }");
|
||||
Files.writeString(
|
||||
file,
|
||||
"class Sleep { public static void main(String[] args) throws Exception { Thread.sleep(10000);"
|
||||
+ " } }");
|
||||
Commands runner = new Commands(Duration.ofMillis(100));
|
||||
try {
|
||||
runner.execute(List.of(System.getProperty("java.home") + "/bin/java",file.toString()),dir,System.getenv(),null,false);
|
||||
runner.execute(
|
||||
List.of(System.getProperty("java.home") + "/bin/java", file.toString()),
|
||||
dir,
|
||||
System.getenv(),
|
||||
null,
|
||||
false);
|
||||
throw new AssertionError("Expected timeout");
|
||||
} catch (Commands.Failure error) { equal(124,error.exitCode); }
|
||||
} finally { Pipeline.deleteTree(dir); }
|
||||
} catch (Commands.Failure error) {
|
||||
equal(124, error.exitCode);
|
||||
}
|
||||
} finally {
|
||||
Pipeline.deleteTree(dir);
|
||||
}
|
||||
});
|
||||
System.out.println(count + " CI tests passed");
|
||||
}
|
||||
|
||||
static class Fixture implements AutoCloseable {
|
||||
final Path root = Files.createTempDirectory("ci-publication-test-");
|
||||
final FakeCommands fake = new FakeCommands();
|
||||
final Pipeline pipeline;
|
||||
|
||||
Fixture() throws Exception {
|
||||
Map<String,String> env = new HashMap<>(System.getenv());
|
||||
env.putAll(Map.of("CI_COMMIT",SHA,"CI_RUN_ID","test","CI_RUN_ATTEMPT","1","CI_REF","refs/heads/main",
|
||||
"CI_REPOSITORY","wanjakrah/agiledancers","TARGET_PLATFORM","linux/amd64","GITEA_SERVER_URL","http://192.168.2.28:3000",
|
||||
"REGISTRY_USER","test","REGISTRY_TOKEN","dummy-token","CI_PUBLISH_LOCK_DIR",root.resolve("lock").toString()));
|
||||
pipeline = new Pipeline(root,env,fake) { @Override String mainHead() { return fake.head; } };
|
||||
Files.writeString(root.resolve("ci/artifacts/verified-commit"),SHA + "\n");
|
||||
Map<String, String> env = new HashMap<>(System.getenv());
|
||||
env.putAll(Map.of(
|
||||
"CI_COMMIT",
|
||||
SHA,
|
||||
"CI_RUN_ID",
|
||||
"test",
|
||||
"CI_RUN_ATTEMPT",
|
||||
"1",
|
||||
"CI_REF",
|
||||
"refs/heads/main",
|
||||
"CI_REPOSITORY",
|
||||
"wanjakrah/agiledancers",
|
||||
"TARGET_PLATFORM",
|
||||
"linux/amd64",
|
||||
"GITEA_SERVER_URL",
|
||||
"http://192.168.2.28:3000",
|
||||
"REGISTRY_USER",
|
||||
"test",
|
||||
"REGISTRY_TOKEN",
|
||||
"dummy-token",
|
||||
"CI_PUBLISH_LOCK_DIR",
|
||||
root.resolve("lock").toString()));
|
||||
pipeline = new Pipeline(root, env, fake) {
|
||||
@Override
|
||||
String mainHead() {
|
||||
return fake.head;
|
||||
}
|
||||
};
|
||||
Files.writeString(root.resolve("ci/artifacts/verified-commit"), SHA + "\n");
|
||||
}
|
||||
|
||||
long calls(String verb) {
|
||||
return fake.calls.stream()
|
||||
.filter(a -> a.size() > 1
|
||||
&& a.getFirst().equals("docker")
|
||||
&& a.get(1).equals(verb))
|
||||
.count();
|
||||
}
|
||||
|
||||
public void close() throws Exception {
|
||||
pipeline.closeDockerConfig();
|
||||
Pipeline.deleteTree(root);
|
||||
}
|
||||
long calls(String verb) { return fake.calls.stream().filter(a -> a.size()>1 && a.getFirst().equals("docker") && a.get(1).equals(verb)).count(); }
|
||||
public void close() throws Exception { pipeline.closeDockerConfig(); Pipeline.deleteTree(root); }
|
||||
}
|
||||
|
||||
static class FakeCommands extends Commands {
|
||||
final List<List<String>> calls = new ArrayList<>();
|
||||
final Map<String,String> images = new HashMap<>(), tags = new HashMap<>();
|
||||
final Map<String, String> images = new HashMap<>(), tags = new HashMap<>();
|
||||
String head = SHA, pushFailure = "", pullFailure = "";
|
||||
boolean manifestError, mavenFailure, lintFailure;
|
||||
boolean dockerAvailable = true;
|
||||
FakeCommands() { super(Duration.ofMinutes(1)); }
|
||||
@Override Result execute(List<String> args, Path cwd, Map<String,String> env, String input, boolean echo) {
|
||||
|
||||
FakeCommands() {
|
||||
super(Duration.ofMinutes(1));
|
||||
}
|
||||
|
||||
@Override
|
||||
Result execute(List<String> args, Path cwd, Map<String, String> env, String input, boolean echo) {
|
||||
calls.add(List.copyOf(args));
|
||||
if (args.getFirst().equals("git")) return new Result(0,SHA);
|
||||
if (args.getFirst().equals("mvn") && mavenFailure) return new Result(42,"deliberate failure");
|
||||
if (args.equals(List.of("npm", "run", "lint")) && lintFailure) return new Result(1,"deliberate lint failure");
|
||||
if (!args.getFirst().equals("docker")) return new Result(0,"");
|
||||
if (args.getFirst().equals("git")) return new Result(0, SHA);
|
||||
if (args.getFirst().equals("mvn") && mavenFailure) return new Result(42, "deliberate failure");
|
||||
if (args.equals(List.of("npm", "run", "lint")) && lintFailure)
|
||||
return new Result(1, "deliberate lint failure");
|
||||
if (!args.getFirst().equals("docker")) return new Result(0, "");
|
||||
String command = args.get(1), ref = args.getLast();
|
||||
if (command.equals("info")) return new Result(dockerAvailable ? 0 : 1,args.contains("{{.Architecture}}") ? "amd64" : "linux");
|
||||
if (command.equals("context")) return new Result(0,"unix:///tmp/test-docker.sock");
|
||||
if (command.equals("info"))
|
||||
return new Result(dockerAvailable ? 0 : 1, args.contains("{{.Architecture}}") ? "amd64" : "linux");
|
||||
if (command.equals("context")) return new Result(0, "unix:///tmp/test-docker.sock");
|
||||
if (command.equals("manifest")) {
|
||||
if (manifestError) return new Result(1,"unauthorized");
|
||||
return images.containsKey(ref) ? new Result(0,"{}") : new Result(1,"manifest unknown");
|
||||
if (manifestError) return new Result(1, "unauthorized");
|
||||
return images.containsKey(ref) ? new Result(0, "{}") : new Result(1, "manifest unknown");
|
||||
}
|
||||
if (command.equals("tag")) tags.put(ref,images.getOrDefault(args.get(2),SHA));
|
||||
if (command.equals("tag")) tags.put(ref, images.getOrDefault(args.get(2), SHA));
|
||||
if (command.equals("push")) {
|
||||
if (!pushFailure.isEmpty() && ref.contains(pushFailure)) return new Result(1,"push failed");
|
||||
images.put(ref,tags.getOrDefault(ref,SHA));
|
||||
if (!pushFailure.isEmpty() && ref.contains(pushFailure)) return new Result(1, "push failed");
|
||||
images.put(ref, tags.getOrDefault(ref, SHA));
|
||||
}
|
||||
if (command.equals("pull") && !pullFailure.isEmpty() && ref.contains(pullFailure)) return new Result(1,"pull failed");
|
||||
if (command.equals("image")) return new Result(0,args.stream().anyMatch(a -> a.contains("RepoDigests"))
|
||||
? ref.substring(0,ref.lastIndexOf(':')) + "@sha256:" + "b".repeat(64) : images.getOrDefault(ref,"missing"));
|
||||
if (command.equals("container") || command.equals("network") && args.contains("inspect")) return new Result(1,"");
|
||||
return new Result(0,"");
|
||||
if (command.equals("pull") && !pullFailure.isEmpty() && ref.contains(pullFailure))
|
||||
return new Result(1, "pull failed");
|
||||
if (command.equals("image"))
|
||||
return new Result(
|
||||
0,
|
||||
args.stream().anyMatch(a -> a.contains("RepoDigests"))
|
||||
? ref.substring(0, ref.lastIndexOf(':')) + "@sha256:" + "b".repeat(64)
|
||||
: images.getOrDefault(ref, "missing"));
|
||||
if (command.equals("container") || command.equals("network") && args.contains("inspect"))
|
||||
return new Result(1, "");
|
||||
return new Result(0, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user