| ... | @@ -1365,6 +1365,8 @@ pub const Manifest = struct { | ... | @@ -1365,6 +1365,8 @@ pub const Manifest = struct { |
| 1365 | if (mem.eql(u8, &disk_file.digest, &input_file.digest)) | 1365 | if (mem.eql(u8, &disk_file.digest, &input_file.digest)) |
| 1366 | return .hit; | 1366 | return .hit; |
| 1367 | } | 1367 | } |
| | 1368 | // From this point in the function, since `input_file.digest` is not populated, it is used as a scratch |
| | 1369 | // space to compute the digest in case stat changed, in order to compare with `disk_file.digest`. |
| 1368 | | 1370 | |
| 1369 | if (input_path.have_stat) { | 1371 | if (input_path.have_stat) { |
| 1370 | const changed = try disk_file.setStatChanged(m, input_file.stat()); | 1372 | const changed = try disk_file.setStatChanged(m, input_file.stat()); |
| ... | @@ -1418,10 +1420,14 @@ pub const Manifest = struct { | ... | @@ -1418,10 +1420,14 @@ pub const Manifest = struct { |
| 1418 | if (input_path.contents.get(m.all_input_content.items)) |file_or_dir_contents| { | 1420 | if (input_path.contents.get(m.all_input_content.items)) |file_or_dir_contents| { |
| 1419 | var hasher = hasher_init; | 1421 | var hasher = hasher_init; |
| 1420 | hasher.update(file_or_dir_contents); | 1422 | hasher.update(file_or_dir_contents); |
| 1421 | hasher.final(&disk_file.digest); | 1423 | hasher.final(&input_file.digest); |
| 1422 | | 1424 | |
| 1423 | if (mem.eql(u8, &disk_file.digest, &input_file.digest)) return .hit; | 1425 | if (mem.eql(u8, &disk_file.digest, &input_file.digest)) { |
| 1424 | return .{ .contents_changed = file_off }; | 1426 | return .hit; |
| | 1427 | } else { |
| | 1428 | @memcpy(&disk_file.digest, &input_file.digest); |
| | 1429 | return .{ .contents_changed = file_off }; |
| | 1430 | } |
| 1425 | } | 1431 | } |
| 1426 | | 1432 | |
| 1427 | if (disk_file.flags.is_directory) { | 1433 | if (disk_file.flags.is_directory) { |
| ... | @@ -1455,7 +1461,7 @@ pub const Manifest = struct { | ... | @@ -1455,7 +1461,7 @@ pub const Manifest = struct { |
| 1455 | } | 1461 | } |
| 1456 | | 1462 | |
| 1457 | const dir_contents_start = m.all_input_content.items.len; | 1463 | const dir_contents_start = m.all_input_content.items.len; |
| 1458 | hashDir(gpa, io, opened_dir, &disk_file.digest, &m.all_input_content) catch |err| switch (err) { | 1464 | hashDir(gpa, io, opened_dir, &input_file.digest, &m.all_input_content) catch |err| switch (err) { |
| 1459 | error.Canceled, error.OutOfMemory => |e| return e, | 1465 | error.Canceled, error.OutOfMemory => |e| return e, |
| 1460 | else => |e| return fail(diag, .{ .file_read = .{ | 1466 | else => |e| return fail(diag, .{ .file_read = .{ |
| 1461 | .file_offset = file_off, | 1467 | .file_offset = file_off, |
| ... | @@ -1471,8 +1477,12 @@ pub const Manifest = struct { | ... | @@ -1471,8 +1477,12 @@ pub const Manifest = struct { |
| 1471 | .populated => unreachable, | 1477 | .populated => unreachable, |
| 1472 | }; | 1478 | }; |
| 1473 | | 1479 | |
| 1474 | if (mem.eql(u8, &disk_file.digest, &input_file.digest)) return .hit; | 1480 | if (mem.eql(u8, &disk_file.digest, &input_file.digest)) { |
| 1475 | return .{ .contents_changed = file_off }; | 1481 | return .hit; |
| | 1482 | } else { |
| | 1483 | @memcpy(&disk_file.digest, &input_file.digest); |
| | 1484 | return .{ .contents_changed = file_off }; |
| | 1485 | } |
| 1476 | } else { | 1486 | } else { |
| 1477 | const opened_file = if (input_path.have_handle) | 1487 | const opened_file = if (input_path.have_handle) |
| 1478 | input_path.handle.file | 1488 | input_path.handle.file |
| ... | @@ -1503,7 +1513,7 @@ pub const Manifest = struct { | ... | @@ -1503,7 +1513,7 @@ pub const Manifest = struct { |
| 1503 | switch (input_path.contents.unwrap()) { | 1513 | switch (input_path.contents.unwrap()) { |
| 1504 | .requested => { | 1514 | .requested => { |
| 1505 | const start = m.all_input_content.items.len; | 1515 | const start = m.all_input_content.items.len; |
| 1506 | hashFileAppend(io, opened_file, &disk_file.digest, &m.all_input_content, gpa) catch |err| switch (err) { | 1516 | hashFileAppend(io, opened_file, &input_file.digest, &m.all_input_content, gpa) catch |err| switch (err) { |
| 1507 | error.Canceled, error.OutOfMemory => |e| return e, | 1517 | error.Canceled, error.OutOfMemory => |e| return e, |
| 1508 | else => |e| return fail(diag, .{ .file_read = .{ | 1518 | else => |e| return fail(diag, .{ .file_read = .{ |
| 1509 | .file_offset = file_off, | 1519 | .file_offset = file_off, |
| ... | @@ -1515,7 +1525,7 @@ pub const Manifest = struct { | ... | @@ -1515,7 +1525,7 @@ pub const Manifest = struct { |
| 1515 | .len = @intCast(m.all_input_content.items.len - start), | 1525 | .len = @intCast(m.all_input_content.items.len - start), |
| 1516 | }); | 1526 | }); |
| 1517 | }, | 1527 | }, |
| 1518 | .unrequested => hashFile(io, opened_file, &disk_file.digest) catch |err| switch (err) { | 1528 | .unrequested => hashFile(io, opened_file, &input_file.digest) catch |err| switch (err) { |
| 1519 | error.Canceled => |e| return e, | 1529 | error.Canceled => |e| return e, |
| 1520 | else => |e| return fail(diag, .{ .file_read = .{ | 1530 | else => |e| return fail(diag, .{ .file_read = .{ |
| 1521 | .file_offset = file_off, | 1531 | .file_offset = file_off, |
| ... | @@ -1525,8 +1535,12 @@ pub const Manifest = struct { | ... | @@ -1525,8 +1535,12 @@ pub const Manifest = struct { |
| 1525 | .populated => unreachable, | 1535 | .populated => unreachable, |
| 1526 | } | 1536 | } |
| 1527 | | 1537 | |
| 1528 | if (mem.eql(u8, &disk_file.digest, &input_file.digest)) return .hit; | 1538 | if (mem.eql(u8, &disk_file.digest, &input_file.digest)) { |
| 1529 | return .{ .contents_changed = file_off }; | 1539 | return .hit; |
| | 1540 | } else { |
| | 1541 | @memcpy(&disk_file.digest, &input_file.digest); |
| | 1542 | return .{ .contents_changed = file_off }; |
| | 1543 | } |
| 1530 | } | 1544 | } |
| 1531 | } | 1545 | } |
| 1532 | | 1546 | |
| ... | @@ -2313,6 +2327,13 @@ fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp { | ... | @@ -2313,6 +2327,13 @@ fn testGetCurrentFileTimestamp(io: Io, dir: Io.Dir) !Io.Timestamp { |
| 2313 | return (try file.stat(io)).mtime; | 2327 | return (try file.stat(io)).mtime; |
| 2314 | } | 2328 | } |
| 2315 | | 2329 | |
| | 2330 | fn testWaitForFileTimestampTick(io: Io, dir: Io.Dir) !void { |
| | 2331 | const initial_time = try testGetCurrentFileTimestamp(io, dir); |
| | 2332 | while ((try testGetCurrentFileTimestamp(io, dir)).nanoseconds == initial_time.nanoseconds) { |
| | 2333 | try Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io); |
| | 2334 | } |
| | 2335 | } |
| | 2336 | |
| 2316 | test "cache file and then recall it" { | 2337 | test "cache file and then recall it" { |
| 2317 | const io = testing.io; | 2338 | const io = testing.io; |
| 2318 | | 2339 | |
| ... | @@ -2332,11 +2353,7 @@ test "cache file and then recall it" { | ... | @@ -2332,11 +2353,7 @@ test "cache file and then recall it" { |
| 2332 | }; | 2353 | }; |
| 2333 | defer testing.allocator.free(tmp_directory.path.?); | 2354 | defer testing.allocator.free(tmp_directory.path.?); |
| 2334 | | 2355 | |
| 2335 | // Wait for file timestamps to tick | 2356 | try testWaitForFileTimestampTick(io, tmp.dir); |
| 2336 | const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir); | | |
| 2337 | while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) { | | |
| 2338 | try Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io); | | |
| 2339 | } | | |
| 2340 | | 2357 | |
| 2341 | var digest1: HexDigest = undefined; | 2358 | var digest1: HexDigest = undefined; |
| 2342 | var digest2: HexDigest = undefined; | 2359 | var digest2: HexDigest = undefined; |
| ... | @@ -2415,11 +2432,7 @@ test "check that changing a file causes cache miss" { | ... | @@ -2415,11 +2432,7 @@ test "check that changing a file causes cache miss" { |
| 2415 | }; | 2432 | }; |
| 2416 | defer testing.allocator.free(tmp_directory.path.?); | 2433 | defer testing.allocator.free(tmp_directory.path.?); |
| 2417 | | 2434 | |
| 2418 | // Wait for file timestamps to tick | 2435 | try testWaitForFileTimestampTick(io, tmp.dir); |
| 2419 | const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir); | | |
| 2420 | while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) { | | |
| 2421 | try Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io); | | |
| 2422 | } | | |
| 2423 | | 2436 | |
| 2424 | var digest1: HexDigest = undefined; | 2437 | var digest1: HexDigest = undefined; |
| 2425 | var digest2: HexDigest = undefined; | 2438 | var digest2: HexDigest = undefined; |
| ... | @@ -2485,6 +2498,91 @@ test "check that changing a file causes cache miss" { | ... | @@ -2485,6 +2498,91 @@ test "check that changing a file causes cache miss" { |
| 2485 | } | 2498 | } |
| 2486 | } | 2499 | } |
| 2487 | | 2500 | |
| | 2501 | test "changed metadata of input file but same digest causes hit" { |
| | 2502 | const io = testing.io; |
| | 2503 | const gpa = testing.allocator; |
| | 2504 | |
| | 2505 | var tmp = testing.tmpDir(.{}); |
| | 2506 | defer tmp.cleanup(); |
| | 2507 | |
| | 2508 | const cwd = try std.process.currentPathAlloc(io, gpa); |
| | 2509 | defer gpa.free(cwd); |
| | 2510 | |
| | 2511 | const input_file_name = "input.txt"; |
| | 2512 | const input_file_contents = "blah blah blah"; |
| | 2513 | const temp_manifest_dir = "cache_hash_change_file_manifest_dir"; |
| | 2514 | |
| | 2515 | try tmp.dir.writeFile(io, .{ .sub_path = input_file_name, .data = input_file_contents }); |
| | 2516 | const tmp_directory: Directory = .{ |
| | 2517 | .path = try std.fs.path.join(gpa, &.{std.testing.TmpDir.parent_dir_path}), |
| | 2518 | .handle = tmp.dir, |
| | 2519 | }; |
| | 2520 | defer gpa.free(tmp_directory.path.?); |
| | 2521 | |
| | 2522 | try testWaitForFileTimestampTick(io, tmp.dir); |
| | 2523 | |
| | 2524 | var digest1: HexDigest = undefined; |
| | 2525 | var digest2: HexDigest = undefined; |
| | 2526 | |
| | 2527 | { |
| | 2528 | var cache: Cache = .{ |
| | 2529 | .io = io, |
| | 2530 | .gpa = gpa, |
| | 2531 | .manifest_dir = try tmp.dir.createDirPathOpen(io, temp_manifest_dir, .{}), |
| | 2532 | .cwd = cwd, |
| | 2533 | }; |
| | 2534 | cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); |
| | 2535 | cache.addPrefix(tmp_directory); |
| | 2536 | defer cache.manifest_dir.close(io); |
| | 2537 | |
| | 2538 | { |
| | 2539 | var man = cache.obtain(); |
| | 2540 | defer man.deinit(); |
| | 2541 | |
| | 2542 | man.hash.addBytes("hHHR7dRt"); |
| | 2543 | try testing.expectEqual( |
| | 2544 | @as(Manifest.InputPath.Index, @fromBackingInt(0)), |
| | 2545 | try man.addInputPath(.{ |
| | 2546 | .root_dir = tmp_directory, |
| | 2547 | .sub_path = input_file_name, |
| | 2548 | }, .{}), |
| | 2549 | ); |
| | 2550 | |
| | 2551 | var diag: Manifest.CheckDiagnostic = undefined; |
| | 2552 | try testing.expectEqual(.incomplete_manifest, try man.check(&diag, .none)); |
| | 2553 | |
| | 2554 | digest1 = man.missDigestHex(); |
| | 2555 | |
| | 2556 | try man.finalize(); |
| | 2557 | } |
| | 2558 | |
| | 2559 | // Same contents, but updates the metadata. |
| | 2560 | try tmp.dir.writeFile(io, .{ .sub_path = input_file_name, .data = input_file_contents }); |
| | 2561 | |
| | 2562 | { |
| | 2563 | var man = cache.obtain(); |
| | 2564 | defer man.deinit(); |
| | 2565 | |
| | 2566 | man.hash.addBytes("hHHR7dRt"); |
| | 2567 | try testing.expectEqual( |
| | 2568 | @as(Manifest.InputPath.Index, @fromBackingInt(0)), |
| | 2569 | try man.addInputPath(.{ |
| | 2570 | .root_dir = tmp_directory, |
| | 2571 | .sub_path = input_file_name, |
| | 2572 | }, .{}), |
| | 2573 | ); |
| | 2574 | |
| | 2575 | var diag: Manifest.CheckDiagnostic = undefined; |
| | 2576 | try testing.expectEqual(.hit, try man.check(&diag, .none)); |
| | 2577 | |
| | 2578 | digest2 = man.hitDigestHex(); |
| | 2579 | try testing.expectEqual(false, man.have_exclusive_lock); |
| | 2580 | } |
| | 2581 | |
| | 2582 | try testing.expectEqualSlices(u8, &digest1, &digest2); |
| | 2583 | } |
| | 2584 | } |
| | 2585 | |
| 2488 | test "no file inputs" { | 2586 | test "no file inputs" { |
| 2489 | const io = testing.io; | 2587 | const io = testing.io; |
| 2490 | | 2588 | |
| ... | @@ -2557,11 +2655,7 @@ test "Manifest with files added after initial hash" { | ... | @@ -2557,11 +2655,7 @@ test "Manifest with files added after initial hash" { |
| 2557 | }; | 2655 | }; |
| 2558 | defer testing.allocator.free(tmp_directory.path.?); | 2656 | defer testing.allocator.free(tmp_directory.path.?); |
| 2559 | | 2657 | |
| 2560 | // Wait for file timestamps to tick | 2658 | try testWaitForFileTimestampTick(io, tmp.dir); |
| 2561 | const initial_time = try testGetCurrentFileTimestamp(io, tmp.dir); | | |
| 2562 | while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time.nanoseconds) { | | |
| 2563 | try Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io); | | |
| 2564 | } | | |
| 2565 | | 2659 | |
| 2566 | var digest1: HexDigest = undefined; | 2660 | var digest1: HexDigest = undefined; |
| 2567 | var digest2: HexDigest = undefined; | 2661 | var digest2: HexDigest = undefined; |
| ... | @@ -2620,11 +2714,7 @@ test "Manifest with files added after initial hash" { | ... | @@ -2620,11 +2714,7 @@ test "Manifest with files added after initial hash" { |
| 2620 | // Modify the file added after initial hash | 2714 | // Modify the file added after initial hash |
| 2621 | try tmp.dir.writeFile(io, .{ .sub_path = temp_file2, .data = "Hello world the second, updated\n" }); | 2715 | try tmp.dir.writeFile(io, .{ .sub_path = temp_file2, .data = "Hello world the second, updated\n" }); |
| 2622 | | 2716 | |
| 2623 | // Wait for file timestamps to tick | 2717 | try testWaitForFileTimestampTick(io, tmp.dir); |
| 2624 | const initial_time2 = try testGetCurrentFileTimestamp(io, tmp.dir); | | |
| 2625 | while ((try testGetCurrentFileTimestamp(io, tmp.dir)).nanoseconds == initial_time2.nanoseconds) { | | |
| 2626 | try Io.Clock.Duration.sleep(.{ .clock = .boot, .raw = .fromNanoseconds(1) }, io); | | |
| 2627 | } | | |
| 2628 | | 2718 | |
| 2629 | { | 2719 | { |
| 2630 | var man = cache.obtain(); | 2720 | var man = cache.obtain(); |