| ... | ... | @@ -1365,6 +1365,8 @@ pub const Manifest = struct { |
| 1365 | 1365 | if (mem.eql(u8, &disk_file.digest, &input_file.digest)) |
| 1366 | 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 | 1371 | if (input_path.have_stat) { |
| 1370 | 1372 | const changed = try disk_file.setStatChanged(m, input_file.stat()); |
| ... | ... | @@ -1418,10 +1420,14 @@ pub const Manifest = struct { |
| 1418 | 1420 | if (input_path.contents.get(m.all_input_content.items)) |file_or_dir_contents| { |
| 1419 | 1421 | var hasher = hasher_init; |
| 1420 | 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; |
| 1424 | | return .{ .contents_changed = file_off }; |
| 1425 | if (mem.eql(u8, &disk_file.digest, &input_file.digest)) { |
| 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 | 1433 | if (disk_file.flags.is_directory) { |
| ... | ... | @@ -1455,7 +1461,7 @@ pub const Manifest = struct { |
| 1455 | 1461 | } |
| 1456 | 1462 | |
| 1457 | 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 | 1465 | error.Canceled, error.OutOfMemory => |e| return e, |
| 1460 | 1466 | else => |e| return fail(diag, .{ .file_read = .{ |
| 1461 | 1467 | .file_offset = file_off, |
| ... | ... | @@ -1471,8 +1477,12 @@ pub const Manifest = struct { |
| 1471 | 1477 | .populated => unreachable, |
| 1472 | 1478 | }; |
| 1473 | 1479 | |
| 1474 | | if (mem.eql(u8, &disk_file.digest, &input_file.digest)) return .hit; |
| 1475 | | return .{ .contents_changed = file_off }; |
| 1480 | if (mem.eql(u8, &disk_file.digest, &input_file.digest)) { |
| 1481 | return .hit; |
| 1482 | } else { |
| 1483 | @memcpy(&disk_file.digest, &input_file.digest); |
| 1484 | return .{ .contents_changed = file_off }; |
| 1485 | } |
| 1476 | 1486 | } else { |
| 1477 | 1487 | const opened_file = if (input_path.have_handle) |
| 1478 | 1488 | input_path.handle.file |
| ... | ... | @@ -1503,7 +1513,7 @@ pub const Manifest = struct { |
| 1503 | 1513 | switch (input_path.contents.unwrap()) { |
| 1504 | 1514 | .requested => { |
| 1505 | 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 | 1517 | error.Canceled, error.OutOfMemory => |e| return e, |
| 1508 | 1518 | else => |e| return fail(diag, .{ .file_read = .{ |
| 1509 | 1519 | .file_offset = file_off, |
| ... | ... | @@ -1515,7 +1525,7 @@ pub const Manifest = struct { |
| 1515 | 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 | 1529 | error.Canceled => |e| return e, |
| 1520 | 1530 | else => |e| return fail(diag, .{ .file_read = .{ |
| 1521 | 1531 | .file_offset = file_off, |
| ... | ... | @@ -1525,8 +1535,12 @@ pub const Manifest = struct { |
| 1525 | 1535 | .populated => unreachable, |
| 1526 | 1536 | } |
| 1527 | 1537 | |
| 1528 | | if (mem.eql(u8, &disk_file.digest, &input_file.digest)) return .hit; |
| 1529 | | return .{ .contents_changed = file_off }; |
| 1538 | if (mem.eql(u8, &disk_file.digest, &input_file.digest)) { |
| 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 | 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 | 2337 | test "cache file and then recall it" { |
| 2317 | 2338 | const io = testing.io; |
| 2318 | 2339 | |
| ... | ... | @@ -2332,11 +2353,7 @@ test "cache file and then recall it" { |
| 2332 | 2353 | }; |
| 2333 | 2354 | defer testing.allocator.free(tmp_directory.path.?); |
| 2334 | 2355 | |
| 2335 | | // Wait for file timestamps to tick |
| 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 | | } |
| 2356 | try testWaitForFileTimestampTick(io, tmp.dir); |
| 2340 | 2357 | |
| 2341 | 2358 | var digest1: HexDigest = undefined; |
| 2342 | 2359 | var digest2: HexDigest = undefined; |
| ... | ... | @@ -2415,11 +2432,7 @@ test "check that changing a file causes cache miss" { |
| 2415 | 2432 | }; |
| 2416 | 2433 | defer testing.allocator.free(tmp_directory.path.?); |
| 2417 | 2434 | |
| 2418 | | // Wait for file timestamps to tick |
| 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 | | } |
| 2435 | try testWaitForFileTimestampTick(io, tmp.dir); |
| 2423 | 2436 | |
| 2424 | 2437 | var digest1: HexDigest = undefined; |
| 2425 | 2438 | var digest2: HexDigest = undefined; |
| ... | ... | @@ -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 | 2586 | test "no file inputs" { |
| 2489 | 2587 | const io = testing.io; |
| 2490 | 2588 | |
| ... | ... | @@ -2557,11 +2655,7 @@ test "Manifest with files added after initial hash" { |
| 2557 | 2655 | }; |
| 2558 | 2656 | defer testing.allocator.free(tmp_directory.path.?); |
| 2559 | 2657 | |
| 2560 | | // Wait for file timestamps to tick |
| 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 | | } |
| 2658 | try testWaitForFileTimestampTick(io, tmp.dir); |
| 2565 | 2659 | |
| 2566 | 2660 | var digest1: HexDigest = undefined; |
| 2567 | 2661 | var digest2: HexDigest = undefined; |
| ... | ... | @@ -2620,11 +2714,7 @@ test "Manifest with files added after initial hash" { |
| 2620 | 2714 | // Modify the file added after initial hash |
| 2621 | 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 |
| 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 | | } |
| 2717 | try testWaitForFileTimestampTick(io, tmp.dir); |
| 2628 | 2718 | |
| 2629 | 2719 | { |
| 2630 | 2720 | var man = cache.obtain(); |