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();