diff --git a/.github/workflows/release_validate.yml b/.github/workflows/release_validate.yml
index 6299715d77..b4711a3b8b 100644
--- a/.github/workflows/release_validate.yml
+++ b/.github/workflows/release_validate.yml
@@ -1,8 +1,13 @@
name: "Release (validate)"
on:
+ workflow_run:
+ workflows: ["Release"]
+ types:
+ - completed
+
schedule:
- - cron: 0 0 0 1 1 ? 1970 # Never for now, planned to run every six hours
+ - cron: 0 */6 * * * # Every six hours
jobs:
validate:
@@ -17,10 +22,7 @@ jobs:
- run: ./scripts/install_zig.sh
- - run: ./zig/zig build dotnet_client:ci -- --release-validate
- - run: ./zig/zig build go_client:ci -- --release-validate
- - run: ./zig/zig build java_client:ci -- --release-validate
- - run: ./zig/zig build node_client:ci -- --release-validate
+ - run: ./zig/zig build ci -- --verify-release
alert_failure:
runs-on: ubuntu-latest
diff --git a/src/clients/dotnet/ci.zig b/src/clients/dotnet/ci.zig
index 8fccb61372..6ffbe66411 100644
--- a/src/clients/dotnet/ci.zig
+++ b/src/clients/dotnet/ci.zig
@@ -85,17 +85,26 @@ pub fn tests(shell: *Shell, gpa: std.mem.Allocator) !void {
}
}
-pub fn verify_release(shell: *Shell, gpa: std.mem.Allocator, tmp_dir: std.fs.Dir) !void {
- var tmp_beetle = try TmpTigerBeetle.init(gpa, .{});
+pub fn validate_release(shell: *Shell, gpa: std.mem.Allocator, options: struct {
+ version: []const u8,
+ tigerbeetle: []const u8,
+}) !void {
+ var tmp_beetle = try TmpTigerBeetle.init(gpa, .{
+ .prebuilt = options.tigerbeetle,
+ });
defer tmp_beetle.deinit(gpa);
+ try shell.env.put("TB_ADDRESS", tmp_beetle.port_str.slice());
+
try shell.exec("dotnet new console", .{});
- try shell.exec("dotnet add package tigerbeetle", .{});
+ try shell.exec("dotnet add package tigerbeetle --version {version}", .{
+ .version = options.version,
+ });
try Shell.copy_path(
shell.project_root,
"src/clients/dotnet/samples/basic/Program.cs",
- tmp_dir,
+ shell.cwd,
"Program.cs",
);
try shell.exec("dotnet run", .{});
diff --git a/src/clients/go/ci.zig b/src/clients/go/ci.zig
index 156b883104..9f0801e3ca 100644
--- a/src/clients/go/ci.zig
+++ b/src/clients/go/ci.zig
@@ -47,17 +47,26 @@ pub fn tests(shell: *Shell, gpa: std.mem.Allocator) !void {
}
}
-pub fn verify_release(shell: *Shell, gpa: std.mem.Allocator, tmp_dir: std.fs.Dir) !void {
- var tmp_beetle = try TmpTigerBeetle.init(gpa, .{});
+pub fn validate_release(shell: *Shell, gpa: std.mem.Allocator, options: struct {
+ version: []const u8,
+ tigerbeetle: []const u8,
+}) !void {
+ var tmp_beetle = try TmpTigerBeetle.init(gpa, .{
+ .prebuilt = options.tigerbeetle,
+ });
defer tmp_beetle.deinit(gpa);
+ try shell.env.put("TB_ADDRESS", tmp_beetle.port_str.slice());
+
try shell.exec("go mod init tbtest", .{});
- try shell.exec("go get github.com/tigerbeetle/tigerbeetle-go", .{});
+ try shell.exec("go get github.com/tigerbeetle/tigerbeetle-go@v{version}", .{
+ .version = options.version,
+ });
try Shell.copy_path(
shell.project_root,
"src/clients/go/samples/basic/main.go",
- tmp_dir,
+ shell.cwd,
"main.go",
);
const zig_exe = try shell.project_root.realpathAlloc(
diff --git a/src/clients/java/ci.zig b/src/clients/java/ci.zig
index 09f3d715c2..81afbadf32 100644
--- a/src/clients/java/ci.zig
+++ b/src/clients/java/ci.zig
@@ -33,11 +33,71 @@ pub fn tests(shell: *Shell, gpa: std.mem.Allocator) !void {
}
}
-pub fn verify_release(shell: *Shell, gpa: std.mem.Allocator, tmp_dir: std.fs.Dir) !void {
- var tmp_beetle = try TmpTigerBeetle.init(gpa, .{});
+pub fn validate_release(shell: *Shell, gpa: std.mem.Allocator, options: struct {
+ version: []const u8,
+ tigerbeetle: []const u8,
+}) !void {
+ var tmp_beetle = try TmpTigerBeetle.init(gpa, .{
+ .prebuilt = options.tigerbeetle,
+ });
defer tmp_beetle.deinit(gpa);
- // TODO
- _ = shell;
- _ = tmp_dir;
+ try shell.env.put("TB_ADDRESS", tmp_beetle.port_str.slice());
+
+ try shell.cwd.writeFile("pom.xml", try shell.print(
+ \\
+ \\ 4.0.0
+ \\ com.tigerbeetle
+ \\ samples
+ \\ 1.0-SNAPSHOT
+ \\
+ \\
+ \\ UTF-8
+ \\ 11
+ \\ 11
+ \\
+ \\
+ \\
+ \\
+ \\
+ \\ org.apache.maven.plugins
+ \\ maven-compiler-plugin
+ \\ 3.8.1
+ \\
+ \\
+ \\ -Xlint:all,-options,-path
+ \\
+ \\
+ \\
+ \\
+ \\
+ \\ org.codehaus.mojo
+ \\ exec-maven-plugin
+ \\ 1.6.0
+ \\
+ \\ com.tigerbeetle.samples.Main
+ \\
+ \\
+ \\
+ \\
+ \\
+ \\
+ \\
+ \\ com.tigerbeetle
+ \\ tigerbeetle-java
+ \\ {s}
+ \\
+ \\
+ \\
+ , .{options.version}));
+
+ try Shell.copy_path(
+ shell.project_root,
+ "src/clients/java/samples/basic/src/main/java/Main.java",
+ shell.cwd,
+ "src/main/java/Main.java",
+ );
+
+ try shell.exec("mvn package", .{});
+ try shell.exec("mvn exec:java", .{});
}
diff --git a/src/clients/java/validation_pom.xml b/src/clients/java/validation_pom.xml
deleted file mode 100644
index 32be884cce..0000000000
--- a/src/clients/java/validation_pom.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
- 4.0.0
-
- com.example
- example
- 1.0
-
-
-
- com.tigerbeetle
- tigerbeetle-java
- {{VERSION}}
-
-
-
diff --git a/src/clients/node/ci.zig b/src/clients/node/ci.zig
index 188d6473d8..a13e26bec0 100644
--- a/src/clients/node/ci.zig
+++ b/src/clients/node/ci.zig
@@ -95,16 +95,25 @@ pub fn tests(shell: *Shell, gpa: std.mem.Allocator) !void {
}
}
-pub fn verify_release(shell: *Shell, gpa: std.mem.Allocator, tmp_dir: std.fs.Dir) !void {
- var tmp_beetle = try TmpTigerBeetle.init(gpa, .{});
+pub fn validate_release(shell: *Shell, gpa: std.mem.Allocator, options: struct {
+ version: []const u8,
+ tigerbeetle: []const u8,
+}) !void {
+ var tmp_beetle = try TmpTigerBeetle.init(gpa, .{
+ .prebuilt = options.tigerbeetle,
+ });
defer tmp_beetle.deinit(gpa);
- try shell.exec("npm install tigerbeetle-node", .{});
+ try shell.env.put("TB_ADDRESS", tmp_beetle.port_str.slice());
+
+ try shell.exec("npm install tigerbeetle-node@{version}", .{
+ .version = options.version,
+ });
try Shell.copy_path(
shell.project_root,
"src/clients/node/samples/basic/main.js",
- tmp_dir,
+ shell.cwd,
"main.js",
);
try shell.exec("node main.js", .{});
diff --git a/src/scripts/ci.zig b/src/scripts/ci.zig
index 33465833f9..16bbb2ed1a 100644
--- a/src/scripts/ci.zig
+++ b/src/scripts/ci.zig
@@ -8,6 +8,7 @@ const builtin = @import("builtin");
const log = std.log;
const assert = std.debug.assert;
+const stdx = @import("../stdx.zig");
const flags = @import("../flags.zig");
const fatal = flags.fatal;
const Shell = @import("../shell.zig");
@@ -23,7 +24,7 @@ const LanguageCI = .{
const CliArgs = struct {
language: ?Language = null,
- verify_release: bool = false,
+ validate_release: bool = false,
};
pub fn main() !void {
@@ -46,36 +47,35 @@ pub fn main() !void {
assert(args.skip());
const cli_args = flags.parse_flags(&args, CliArgs);
+ if (cli_args.validate_release) {
+ try validate_release(shell, gpa, cli_args.language);
+ } else {
+ try run_tests(shell, gpa, cli_args.language);
+ }
+}
+
+fn run_tests(shell: *Shell, gpa: std.mem.Allocator, language_requested: ?Language) !void {
inline for (comptime std.enums.values(Language)) |language| {
- if (cli_args.language == language or cli_args.language == null) {
+ if (language_requested == language or language_requested == null) {
const ci = @field(LanguageCI, @tagName(language));
- if (cli_args.verify_release) {
- var tmp_dir = std.testing.tmpDir(.{});
- defer tmp_dir.cleanup();
+ var section = try shell.open_section(@tagName(language) ++ " ci");
+ defer section.close();
- try tmp_dir.dir.setAsCwd();
+ {
+ try shell.pushd("./src/clients/" ++ @tagName(language));
+ defer shell.popd();
- try ci.verify_release(shell, gpa, tmp_dir.dir);
- } else {
- var section = try shell.open_section(@tagName(language) ++ " ci");
- defer section.close();
-
- {
- try shell.pushd("./src/clients/" ++ @tagName(language));
- defer shell.popd();
-
- try ci.tests(shell, gpa);
- }
+ try ci.tests(shell, gpa);
+ }
- // Piggy back on node client testing to verify our docs, as we use node to generate
- // them anyway.
- if (language == .node and builtin.os.tag == .linux) {
- const node_version = try shell.exec_stdout("node --version", .{});
- if (std.mem.startsWith(u8, node_version, "v14")) {
- log.warn("skip building documentation on old Node.js", .{});
- } else {
- try build_docs(shell);
- }
+ // Piggy back on node client testing to verify our docs, as we use node to generate
+ // them anyway.
+ if (language == .node and builtin.os.tag == .linux) {
+ const node_version = try shell.exec_stdout("node --version", .{});
+ if (std.mem.startsWith(u8, node_version, "v14")) {
+ log.warn("skip building documentation on old Node.js", .{});
+ } else {
+ try build_docs(shell);
}
}
}
@@ -89,3 +89,51 @@ fn build_docs(shell: *Shell) !void {
try shell.exec("npm install", .{});
try shell.exec("npm run build", .{});
}
+
+fn validate_release(shell: *Shell, gpa: std.mem.Allocator, language_requested: ?Language) !void {
+ var tmp_dir = std.testing.tmpDir(.{});
+ defer tmp_dir.cleanup();
+
+ try shell.pushd_dir(tmp_dir.dir);
+ defer shell.popd();
+
+ const release_info = try shell.exec_stdout(
+ "gh release --repo tigerbeetle/tigerbeetle list --limit 1",
+ .{},
+ );
+ const tag = stdx.cut(release_info, "\t").?.prefix;
+ log.info("validateing release {s}", .{tag});
+
+ try shell.exec(
+ "gh release --repo tigerbeetle/tigerbeetle download {tag}",
+ .{ .tag = tag },
+ );
+
+ if (builtin.os.tag != .linux) {
+ log.warn("skip release verification for platforms other than Linux", .{});
+ }
+
+ try shell.exec("unzip tigerbeetle-x86_64-linux.zip", .{});
+ const version = try shell.exec_stdout("./tigerbeetle version --verbose", .{});
+ assert(std.mem.indexOf(u8, version, tag) != null);
+ assert(std.mem.indexOf(u8, version, "ReleaseSafe") != null);
+
+ const tigerbeetle_absolute_path = try shell.cwd.realpathAlloc(gpa, "tigerbeetle");
+ defer gpa.free(tigerbeetle_absolute_path);
+
+ inline for (comptime std.enums.values(Language)) |language| {
+ if (language_requested == language or language_requested == null) {
+ const ci = @field(LanguageCI, @tagName(language));
+ try ci.validate_release(shell, gpa, .{
+ .tigerbeetle = tigerbeetle_absolute_path,
+ .version = tag,
+ });
+ }
+ }
+
+ const docker_version = try shell.exec_stdout(
+ \\docker run ghcr.io/tigerbeetle/tigerbeetle:{version} version --verbose
+ , .{ .version = tag });
+ assert(std.mem.indexOf(u8, docker_version, tag) != null);
+ assert(std.mem.indexOf(u8, docker_version, "ReleaseSafe") != null);
+}
diff --git a/src/shell.zig b/src/shell.zig
index aebcd057ee..83ec59599c 100644
--- a/src/shell.zig
+++ b/src/shell.zig
@@ -201,6 +201,17 @@ pub fn pushd(shell: *Shell, path: []const u8) !void {
shell.cwd = cwd_new;
}
+pub fn pushd_dir(shell: *Shell, dir: std.fs.Dir) !void {
+ assert(shell.cwd_stack_count < cwd_stack_max);
+
+ // Re-open the directory such that `popd` can close it.
+ const cwd_new = try dir.openDir(".", .{});
+
+ shell.cwd_stack[shell.cwd_stack_count] = shell.cwd;
+ shell.cwd_stack_count += 1;
+ shell.cwd = cwd_new;
+}
+
pub fn popd(shell: *Shell) void {
shell.cwd.close();
shell.cwd_stack_count -= 1;
diff --git a/src/testing/tmp_tigerbeetle.zig b/src/testing/tmp_tigerbeetle.zig
index a5f9291f6d..b12588c802 100644
--- a/src/testing/tmp_tigerbeetle.zig
+++ b/src/testing/tmp_tigerbeetle.zig
@@ -33,24 +33,36 @@ stderr_reader_thread: std.Thread,
pub fn init(
gpa: std.mem.Allocator,
- options: struct { echo: bool = true },
+ options: struct {
+ echo: bool = true,
+ prebuilt: ?[]const u8 = null,
+ },
) !TmpTigerBeetle {
const shell = try Shell.create(gpa);
defer shell.destroy();
- const tigerbeetle_exe = comptime "tigerbeetle" ++ builtin.target.exeFileExt();
+ var from_source_path: ?[]const u8 = null;
+ defer if (from_source_path) |path| gpa.free(path);
- // If tigerbeetle binary does not exist yet, build it.
- // TODO: just run `zig build run` unconditionally here, when that doesn't do spurious rebuilds.
- _ = shell.project_root.statFile(tigerbeetle_exe) catch {
- log.info("building TigerBeetle", .{});
- try shell.zig("build", .{});
+ if (options.prebuilt == null) {
+ const tigerbeetle_exe = comptime "tigerbeetle" ++ builtin.target.exeFileExt();
- _ = try shell.project_root.statFile(tigerbeetle_exe);
- };
+ // If tigerbeetle binary does not exist yet, build it.
+ //
+ // TODO: just run `zig build run` unconditionally here, when that doesn't do spurious
+ // rebuilds.
+ _ = shell.project_root.statFile(tigerbeetle_exe) catch {
+ log.info("building TigerBeetle", .{});
+ try shell.zig("build", .{});
+
+ _ = try shell.project_root.statFile(tigerbeetle_exe);
+ };
+
+ from_source_path = try shell.project_root.realpathAlloc(gpa, tigerbeetle_exe);
+ }
- const tigerbeetle: []const u8 = try shell.project_root.realpathAlloc(gpa, tigerbeetle_exe);
- defer gpa.free(tigerbeetle);
+ const tigerbeetle: []const u8 = options.prebuilt orelse from_source_path.?;
+ assert(std.fs.path.isAbsolute(tigerbeetle));
var tmp_dir = std.testing.tmpDir(.{});
errdefer tmp_dir.cleanup();