Repository navigation
fix(floyd-warshall): cycle guard in path reconstruction (hang + OOM) #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -157,12 +157,22 @@ pub fn FloydWarshallOptimized(comptime config: Config) type { | |
| /// Returns error.NoPathFound if no path exists between the nodes | ||
| pub fn setPathWithMapping(self: *const Self, path_list: *std.array_list.Managed(u32), u_node: u32, v_node: u32) PathError!void { | ||
| var current = u_node; | ||
| var steps: usize = 0; | ||
| const max_steps: usize = self.size; // a shortest path visits each node at most once | ||
| while (current != v_node) { | ||
| // Cycle / corrupt next-hop guard: without this, an inconsistent | ||
| // `next` matrix (a cycle that never reaches `v_node`) loops | ||
| // forever, growing `path_list` unbounded — a hard hang + OOM. | ||
| // A real shortest path is at most `size` hops, so anything | ||
| // longer is a cycle. Surfaced as a permanent freeze + memory | ||
| // blow-up loading a 1000-worker flying-platform colony. | ||
| if (steps > max_steps) return error.NoPathFound; | ||
| try path_list.append(current); | ||
| current = self.nextWithMapping(current, v_node); | ||
| if (current == INF) { | ||
| return error.NoPathFound; | ||
| } | ||
| steps += 1; | ||
| } | ||
|
Comment on lines
+160
to
176
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The cycle guard condition Checking |
||
| try path_list.append(v_node); | ||
| } | ||
|
|
@@ -171,12 +181,18 @@ pub fn FloydWarshallOptimized(comptime config: Config) type { | |
| /// Returns error.NoPathFound if no path exists between the nodes | ||
| pub fn setPathWithMappingUnmanaged(self: *const Self, allocator: std.mem.Allocator, path_list: *std.ArrayListUnmanaged(u32), u_node: u32, v_node: u32) PathError!void { | ||
| var current = u_node; | ||
| var steps: usize = 0; | ||
| const max_steps: usize = self.size; // a shortest path visits each node at most once | ||
| while (current != v_node) { | ||
| // See `setPathWithMapping` — guards an inconsistent `next` | ||
| // matrix cycle from looping forever (hang + unbounded alloc). | ||
| if (steps > max_steps) return error.NoPathFound; | ||
| try path_list.append(allocator, current); | ||
| current = self.nextWithMapping(current, v_node); | ||
| if (current == INF) { | ||
| return error.NoPathFound; | ||
| } | ||
| steps += 1; | ||
| } | ||
|
Comment on lines
+184
to
196
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similarly to |
||
| try path_list.append(allocator, v_node); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,181 @@ | ||
| const std = @import("std"); | ||
|
|
||
| pub fn build(b: *std.Build) void { | ||
| const target = b.standardTargetOptions(.{}); | ||
| const optimize = b.standardOptimizeOption(.{}); | ||
|
|
||
| // Library module — link_libc=true because runner.zig/junit.zig use | ||
| // std.c.{open,write,close,getenv} on POSIX (and Win32 directly on | ||
| // Windows). Without this, downstream test binaries that import zspec | ||
| // get a libc-link error on Linux. | ||
| const zspec_mod = b.addModule("zspec", .{ | ||
| .root_source_file = b.path("src/zspec.zig"), | ||
| .target = target, | ||
| .optimize = optimize, | ||
| .link_libc = true, | ||
| }); | ||
|
|
||
| // Optional ECS integration module | ||
| const zspec_ecs_mod = b.addModule("zspec-ecs", .{ | ||
| .root_source_file = b.path("src/integrations/ecs.zig"), | ||
| .target = target, | ||
| .optimize = optimize, | ||
| }); | ||
|
|
||
| // Optional FSM integration module | ||
| const zspec_fsm_mod = b.addModule("zspec-fsm", .{ | ||
| .root_source_file = b.path("src/integrations/fsm.zig"), | ||
| .target = target, | ||
| .optimize = optimize, | ||
| }); | ||
|
|
||
| // Unit tests for zspec itself | ||
| const lib_unit_tests = b.addTest(.{ | ||
| .root_module = b.createModule(.{ | ||
| .root_source_file = b.path("src/zspec.zig"), | ||
| .target = target, | ||
| .optimize = optimize, | ||
| .link_libc = true, | ||
| }), | ||
| }); | ||
|
|
||
| const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); | ||
|
|
||
| // Unit tests for the JUnit XML writer. Lives in its own test exe because | ||
| // `src/runner.zig` (used as the test_runner for the example/factory test | ||
| // suites) also imports `junit.zig`; pulling it in via `src/zspec.zig` | ||
| // would make the same file belong to both the `root` and `zspec` modules. | ||
| const junit_unit_tests = b.addTest(.{ | ||
| .root_module = b.createModule(.{ | ||
| .root_source_file = b.path("src/junit.zig"), | ||
| .target = target, | ||
| .optimize = optimize, | ||
| .link_libc = true, | ||
| }), | ||
| }); | ||
|
|
||
| const run_junit_unit_tests = b.addRunArtifact(junit_unit_tests); | ||
|
|
||
| // Example tests using zspec | ||
| const example_tests = b.addTest(.{ | ||
| .root_module = b.createModule(.{ | ||
| .root_source_file = b.path("tests/example_test.zig"), | ||
| .target = target, | ||
| .optimize = optimize, | ||
| .link_libc = true, | ||
| .imports = &.{ | ||
| .{ .name = "zspec", .module = zspec_mod }, | ||
| }, | ||
| }), | ||
| .test_runner = .{ .path = b.path("src/runner.zig"), .mode = .simple }, | ||
| }); | ||
|
|
||
| const run_example_tests = b.addRunArtifact(example_tests); | ||
|
|
||
| // Factory union tests (issue #29) | ||
| const factory_union_tests = b.addTest(.{ | ||
| .root_module = b.createModule(.{ | ||
| .root_source_file = b.path("tests/factory_union_test.zig"), | ||
| .target = target, | ||
| .optimize = optimize, | ||
| .link_libc = true, | ||
| .imports = &.{ | ||
| .{ .name = "zspec", .module = zspec_mod }, | ||
| }, | ||
| }), | ||
| .test_runner = .{ .path = b.path("src/runner.zig"), .mode = .simple }, | ||
| }); | ||
|
|
||
| const run_factory_union_tests = b.addRunArtifact(factory_union_tests); | ||
|
|
||
| // Factory .zon loading tests (issue #31) | ||
| const factory_zon_tests = b.addTest(.{ | ||
| .root_module = b.createModule(.{ | ||
| .root_source_file = b.path("tests/factory_zon_test.zig"), | ||
| .target = target, | ||
| .optimize = optimize, | ||
| .link_libc = true, | ||
| .imports = &.{ | ||
| .{ .name = "zspec", .module = zspec_mod }, | ||
| }, | ||
| }), | ||
| .test_runner = .{ .path = b.path("src/runner.zig"), .mode = .simple }, | ||
| }); | ||
|
|
||
| const run_factory_zon_tests = b.addRunArtifact(factory_zon_tests); | ||
|
|
||
| // Fixture tests (RFC 001 / issue #38) | ||
| const fixture_tests = b.addTest(.{ | ||
| .root_module = b.createModule(.{ | ||
| .root_source_file = b.path("tests/fixture_test.zig"), | ||
| .target = target, | ||
| .optimize = optimize, | ||
| .link_libc = true, | ||
| .imports = &.{ | ||
| .{ .name = "zspec", .module = zspec_mod }, | ||
| }, | ||
| }), | ||
| .test_runner = .{ .path = b.path("src/runner.zig"), .mode = .simple }, | ||
| }); | ||
|
|
||
| const run_fixture_tests = b.addRunArtifact(fixture_tests); | ||
|
|
||
| const test_step = b.step("test", "Run unit tests"); | ||
| test_step.dependOn(&run_lib_unit_tests.step); | ||
| test_step.dependOn(&run_junit_unit_tests.step); | ||
| test_step.dependOn(&run_fixture_tests.step); | ||
| test_step.dependOn(&run_factory_union_tests.step); | ||
| test_step.dependOn(&run_factory_zon_tests.step); | ||
|
|
||
| const example_step = b.step("example", "Run example tests"); | ||
| example_step.dependOn(&run_example_tests.step); | ||
| example_step.dependOn(&run_factory_union_tests.step); | ||
| example_step.dependOn(&run_factory_zon_tests.step); | ||
| example_step.dependOn(&run_fixture_tests.step); | ||
|
|
||
| // Examples - individual example files | ||
| const example_files = [_]struct { name: []const u8, path: []const u8 }{ | ||
| .{ .name = "examples-basic", .path = "examples/basic_test.zig" }, | ||
| .{ .name = "examples-hooks", .path = "examples/hooks_test.zig" }, | ||
| .{ .name = "examples-let", .path = "examples/let_memoization_test.zig" }, | ||
| .{ .name = "examples-matchers", .path = "examples/matchers_test.zig" }, | ||
| .{ .name = "examples-factory", .path = "examples/factory_test.zig" }, | ||
| .{ .name = "examples-factory-zon", .path = "examples/factory_zon_test.zig" }, | ||
| .{ .name = "examples-fixture", .path = "examples/fixture_test.zig" }, | ||
| .{ .name = "examples-nested", .path = "examples/nested_contexts_test.zig" }, | ||
| .{ .name = "examples-ecs", .path = "examples/ecs_integration_test.zig" }, | ||
| .{ .name = "examples-fsm", .path = "examples/fsm_integration_test.zig" }, | ||
| }; | ||
|
|
||
| const examples_all_step = b.step("examples", "Run all examples"); | ||
|
|
||
| for (example_files) |ex| { | ||
| // Integration examples need the optional modules | ||
| const needs_integrations = std.mem.indexOf(u8, ex.name, "-ecs") != null or | ||
| std.mem.indexOf(u8, ex.name, "-fsm") != null; | ||
|
|
||
| const imports = if (needs_integrations) &[_]std.Build.Module.Import{ | ||
| .{ .name = "zspec", .module = zspec_mod }, | ||
| .{ .name = "zspec-ecs", .module = zspec_ecs_mod }, | ||
| .{ .name = "zspec-fsm", .module = zspec_fsm_mod }, | ||
| } else &[_]std.Build.Module.Import{ | ||
| .{ .name = "zspec", .module = zspec_mod }, | ||
| }; | ||
|
|
||
| const ex_test = b.addTest(.{ | ||
| .root_module = b.createModule(.{ | ||
| .root_source_file = b.path(ex.path), | ||
| .target = target, | ||
| .optimize = optimize, | ||
| .link_libc = true, | ||
| .imports = imports, | ||
| }), | ||
| .test_runner = .{ .path = b.path("src/runner.zig"), .mode = .simple }, | ||
| }); | ||
|
|
||
| const run_ex = b.addRunArtifact(ex_test); | ||
| const ex_step = b.step(ex.name, b.fmt("Run {s}", .{ex.path})); | ||
| ex_step.dependOn(&run_ex.step); | ||
| examples_all_step.dependOn(&run_ex.step); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| .{ | ||
| .name = .zspec, | ||
| .version = "0.9.1", | ||
| .fingerprint = 0x940ac7516d8ba28d, | ||
| .paths = .{ | ||
| "build.zig", | ||
| "build.zig.zon", | ||
| "src", | ||
| "tests", | ||
| "examples", | ||
| }, | ||
| .dependencies = .{}, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| //! Basic ZSpec Example | ||
| //! | ||
| //! Demonstrates the simplest way to write tests with ZSpec: | ||
| //! - Importing and using zspec | ||
| //! - Basic test structure | ||
| //! - Simple assertions | ||
|
|
||
| const std = @import("std"); | ||
| const zspec = @import("zspec"); | ||
| const expect = zspec.expect; | ||
|
|
||
| // This test block triggers ZSpec to discover all tests in this file | ||
| test { | ||
| zspec.runAll(@This()); | ||
| } | ||
|
|
||
| // Simple standalone tests (not in a context struct) | ||
| test "addition works correctly" { | ||
| const result = 2 + 2; | ||
| try expect.equal(result, 4); | ||
| } | ||
|
|
||
| test "strings can be compared" { | ||
| const greeting = "hello"; | ||
| try expect.equal(greeting, "hello"); | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
zig-pkg/directory containing the source code of thezspecdependency seems to have been accidentally committed to the repository. Sincezspecis already declared as a package dependency inbuild.zig.zonand is automatically fetched by the Zig package manager, committing its source code underzig-pkg/is redundant and bloats the repository.Please remove the
zig-pkg/directory from the repository and add it to your.gitignorefile to prevent it from being committed in the future.