From b422e4a202f3b4d6e33c1433c3d27152dc5bc925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Anic=CC=81?= Date: Thu, 4 Apr 2024 21:28:28 +0200 Subject: [PATCH] fetch: save test cases Prepare test cases, store them in Fetch/testdata. They cover changes in this PR as well from previous one #19111. --- src/Package/Fetch.zig | 162 ++++++++++-------- .../Fetch/testdata/duplicate_paths.tar.gz | Bin 0 -> 3230 bytes .../testdata/duplicate_paths_excluded.tar.gz | Bin 0 -> 3237 bytes src/Package/Fetch/testdata/no_root.tar.gz | Bin 0 -> 3172 bytes 4 files changed, 88 insertions(+), 74 deletions(-) create mode 100644 src/Package/Fetch/testdata/duplicate_paths.tar.gz create mode 100644 src/Package/Fetch/testdata/duplicate_paths_excluded.tar.gz create mode 100644 src/Package/Fetch/testdata/no_root.tar.gz diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index a841a754f3..a393c7835e 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -1890,13 +1890,27 @@ const UnpackResult = struct { } }; -test "tarball with duplicate file names" { +test "tarball with duplicate paths" { + // This tarball has duplicate path 'dir1/file1' to simulate case sensitve + // file system on any file sytstem. + // + // duplicate_paths/ + // duplicate_paths/dir1/ + // duplicate_paths/dir1/file1 + // duplicate_paths/dir1/file1 + // duplicate_paths/build.zig.zon + // duplicate_paths/src/ + // duplicate_paths/src/main.zig + // duplicate_paths/src/root.zig + // duplicate_paths/build.zig + // + const gpa = std.testing.allocator; var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); - const tarball_name = "package.tar"; - try createTestTarball(tmp.dir, tarball_name, false); + const tarball_name = "duplicate_paths.tar.gz"; + try saveEmbedFile(tarball_name, tmp.dir); const tarball_path = try std.fmt.allocPrint(gpa, "zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name }); defer gpa.free(tarball_path); @@ -1906,32 +1920,87 @@ test "tarball with duplicate file names" { defer fb.deinit(); try std.testing.expectError(error.FetchFailed, fetch.run()); - try fb.expectFetchErrors(2, + try fb.expectFetchErrors(1, \\error: unable to unpack tarball - \\ note: unable to create file 'dir/file': PathAlreadyExists \\ note: unable to create file 'dir1/file1': PathAlreadyExists \\ ); } -test "tarball with error paths excluded" { +test "tarball with excluded duplicate paths" { + // Same as previous tarball but has build.zig.zon wich excludes 'dir1'. + // + // .paths = .{ + // "build.zig", + // "build.zig.zon", + // "src", + // } + // + const gpa = std.testing.allocator; var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); - const tarball_name = "package.tar"; - try createTestTarball(tmp.dir, tarball_name, true); + const tarball_name = "duplicate_paths_excluded.tar.gz"; + try saveEmbedFile(tarball_name, tmp.dir); const tarball_path = try std.fmt.allocPrint(gpa, "zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name }); defer gpa.free(tarball_path); // Run tarball fetch, should succeed var fb: TestFetchBuilder = undefined; - var fetch = try fb.build(std.testing.allocator, tmp.dir, tarball_path); + var fetch = try fb.build(gpa, tmp.dir, tarball_path); defer fb.deinit(); try fetch.run(); const hex_digest = Package.Manifest.hexDigest(fetch.actual_hash); - try std.testing.expectEqualStrings("122022afac878639d5ea6fcca14a123e21fd0395c1f2ef2c89017fa71390f73024af", &hex_digest); + try std.testing.expectEqualStrings( + "12200bafe035cbb453dd717741b66e9f9d1e6c674069d06121dafa1b2e62eb6b22da", + &hex_digest, + ); + + const expected_files: []const []const u8 = &.{ + "build.zig", + "build.zig.zon", + "src/main.zig", + "src/root.zig", + }; + try fb.expectPackageFiles(expected_files); +} + +test "tarball without root folder" { + // Tarball with root folder. Manifest excludes dir1 and dir2. + // + // build.zig + // build.zig.zon + // dir1/ + // dir1/file2 + // dir1/file1 + // dir2/ + // dir2/file2 + // src/ + // src/main.zig + // + + const gpa = std.testing.allocator; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + const tarball_name = "no_root.tar.gz"; + try saveEmbedFile(tarball_name, tmp.dir); + const tarball_path = try std.fmt.allocPrint(gpa, "zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name }); + defer gpa.free(tarball_path); + + // Run tarball fetch, should succeed + var fb: TestFetchBuilder = undefined; + var fetch = try fb.build(gpa, tmp.dir, tarball_path); + defer fb.deinit(); + try fetch.run(); + + const hex_digest = Package.Manifest.hexDigest(fetch.actual_hash); + try std.testing.expectEqualStrings( + "12209f939bfdcb8b501a61bb4a43124dfa1b2848adc60eec1e4624c560357562b793", + &hex_digest, + ); const expected_files: []const []const u8 = &.{ "build.zig", @@ -1941,6 +2010,14 @@ test "tarball with error paths excluded" { try fb.expectPackageFiles(expected_files); } +fn saveEmbedFile(comptime tarball_name: []const u8, dir: fs.Dir) !void { + //const tarball_name = "duplicate_paths_excluded.tar.gz"; + const tarball_content = @embedFile("Fetch/testdata/" ++ tarball_name); + var tmp_file = try dir.createFile(tarball_name, .{}); + defer tmp_file.close(); + try tmp_file.writeAll(tarball_content); +} + // Builds Fetch with required dependencies, clears dependencies on deinit(). const TestFetchBuilder = struct { thread_pool: ThreadPool, @@ -1981,7 +2058,7 @@ const TestFetchBuilder = struct { .hash_tok = 0, .name_tok = 0, .lazy_status = .eager, - .parent_package_root = Cache.Path{ .root_dir = undefined }, + .parent_package_root = Cache.Path{ .root_dir = Cache.Directory{ .handle = cache_dir, .path = null } }, .parent_manifest_ast = null, .prog_node = self.progress.start("Fetch", 0), .job_queue = &self.job_queue, @@ -2061,66 +2138,3 @@ const TestFetchBuilder = struct { try std.testing.expectEqualStrings(msg, al.items); } }; - -// Creates tarball with duplicate files names. Simulating case collisions on -// case insensitive file system without use of that kind of the file system. -// Manifest will exclude those files, so adding manifest should remove duplicate -// files problem. -fn createTestTarball(dir: fs.Dir, tarball_name: []const u8, with_manifest: bool) !void { - const file = try dir.createFile(tarball_name, .{}); - defer file.close(); - - const TarHeader = std.tar.output.Header; - const prefix = tarball_name; - - // add root directory - { - var hdr = TarHeader.init(); - hdr.typeflag = .directory; - try hdr.setPath(prefix, ""); - try hdr.updateChecksum(); - try file.writeAll(std.mem.asBytes(&hdr)); - } - - // add files - const files: []const []const u8 = &.{ - "build.zig", - "src/main.zig", - // duplicate file paths - "dir/file", - "dir1/file1", - "dir/file", - "dir1/file1", - }; - - for (files) |path| { - var hdr = TarHeader.init(); - hdr.typeflag = .regular; - try hdr.setPath(prefix, path); - try hdr.updateChecksum(); - try file.writeAll(std.mem.asBytes(&hdr)); - } - - // add manifest - if (with_manifest) { - const build_zig_zon = - \\ .{ - \\ .name = "fetch", - \\ .version = "0.0.0", - \\ .paths = .{ - \\ "src", - \\ "build.zig", - \\ "build.zig.zon" - \\ }, - \\ } - ; - var hdr = TarHeader.init(); - hdr.typeflag = .regular; - try hdr.setPath(prefix, "build.zig.zon"); - try hdr.setSize(build_zig_zon.len); - try hdr.updateChecksum(); - try file.writeAll(std.mem.asBytes(&hdr)); - try file.writeAll(build_zig_zon); - try file.writeAll(&[_]u8{0} ** (512 - build_zig_zon.len)); - } -} diff --git a/src/Package/Fetch/testdata/duplicate_paths.tar.gz b/src/Package/Fetch/testdata/duplicate_paths.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..118a934c1b03d764e4854f9aeb60ed684467caad GIT binary patch literal 3230 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 q7c?*7ms}&a>+RH{>j(a=X