|
1 | 1 | #include "nix/store/references.hh" |
| 2 | +#include "nix/store/path-references.hh" |
| 3 | +#include "nix/util/memory-source-accessor.hh" |
2 | 4 |
|
3 | 5 | #include <gtest/gtest.h> |
4 | 6 |
|
@@ -79,4 +81,145 @@ TEST(references, scan) |
79 | 81 | } |
80 | 82 | } |
81 | 83 |
|
| 84 | +TEST(references, scanForReferencesDeep) |
| 85 | +{ |
| 86 | + using File = MemorySourceAccessor::File; |
| 87 | + |
| 88 | + // Create store paths to search for |
| 89 | + StorePath path1{"dc04vv14dak1c1r48qa0m23vr9jy8sm0-foo"}; |
| 90 | + StorePath path2{"zc842j0rz61mjsp3h3wp5ly71ak6qgdn-bar"}; |
| 91 | + StorePath path3{"a5cn2i4b83gnsm60d38l3kgb8qfplm11-baz"}; |
| 92 | + |
| 93 | + StorePathSet refs{path1, path2, path3}; |
| 94 | + |
| 95 | + std::string_view hash1 = path1.hashPart(); |
| 96 | + std::string_view hash2 = path2.hashPart(); |
| 97 | + std::string_view hash3 = path3.hashPart(); |
| 98 | + |
| 99 | + // Create an in-memory file system with various reference patterns |
| 100 | + auto accessor = make_ref<MemorySourceAccessor>(); |
| 101 | + accessor->root = File::Directory{ |
| 102 | + .contents{ |
| 103 | + { |
| 104 | + // file1.txt: contains hash1 |
| 105 | + "file1.txt", |
| 106 | + File::Regular{ |
| 107 | + .contents = "This file references " + hash1 + " in its content", |
| 108 | + }, |
| 109 | + }, |
| 110 | + { |
| 111 | + // file2.txt: contains hash2 and hash3 |
| 112 | + "file2.txt", |
| 113 | + File::Regular{ |
| 114 | + .contents = "Multiple refs: " + hash2 + " and also " + hash3, |
| 115 | + }, |
| 116 | + }, |
| 117 | + { |
| 118 | + // file3.txt: contains no references |
| 119 | + "file3.txt", |
| 120 | + File::Regular{ |
| 121 | + .contents = "This file has no store path references at all", |
| 122 | + }, |
| 123 | + }, |
| 124 | + { |
| 125 | + // subdir: a subdirectory |
| 126 | + "subdir", |
| 127 | + File::Directory{ |
| 128 | + .contents{ |
| 129 | + { |
| 130 | + // subdir/file4.txt: contains hash1 again |
| 131 | + "file4.txt", |
| 132 | + File::Regular{ |
| 133 | + .contents = "Subdirectory file with " + hash1, |
| 134 | + }, |
| 135 | + }, |
| 136 | + }, |
| 137 | + }, |
| 138 | + }, |
| 139 | + { |
| 140 | + // link1: a symlink that contains a reference in its target |
| 141 | + "link1", |
| 142 | + File::Symlink{ |
| 143 | + .target = hash2 + "-target", |
| 144 | + }, |
| 145 | + }, |
| 146 | + }, |
| 147 | + }; |
| 148 | + |
| 149 | + // Test the callback-based API |
| 150 | + { |
| 151 | + std::map<CanonPath, StorePathSet> foundRefs; |
| 152 | + |
| 153 | + scanForReferencesDeep(*accessor, CanonPath::root, refs, [&](FileRefScanResult result) { |
| 154 | + foundRefs[std::move(result.filePath)] = std::move(result.foundRefs); |
| 155 | + }); |
| 156 | + |
| 157 | + // Verify we found the expected references |
| 158 | + EXPECT_EQ(foundRefs.size(), 4); // file1, file2, file4, link1 |
| 159 | + |
| 160 | + // Check file1.txt found path1 |
| 161 | + { |
| 162 | + CanonPath f1Path("/file1.txt"); |
| 163 | + auto it = foundRefs.find(f1Path); |
| 164 | + ASSERT_TRUE(it != foundRefs.end()); |
| 165 | + EXPECT_EQ(it->second.size(), 1); |
| 166 | + EXPECT_TRUE(it->second.count(path1)); |
| 167 | + } |
| 168 | + |
| 169 | + // Check file2.txt found path2 and path3 |
| 170 | + { |
| 171 | + CanonPath f2Path("/file2.txt"); |
| 172 | + auto it = foundRefs.find(f2Path); |
| 173 | + ASSERT_TRUE(it != foundRefs.end()); |
| 174 | + EXPECT_EQ(it->second.size(), 2); |
| 175 | + EXPECT_TRUE(it->second.count(path2)); |
| 176 | + EXPECT_TRUE(it->second.count(path3)); |
| 177 | + } |
| 178 | + |
| 179 | + // Check file3.txt is not in results (no refs) |
| 180 | + { |
| 181 | + CanonPath f3Path("/file3.txt"); |
| 182 | + EXPECT_FALSE(foundRefs.count(f3Path)); |
| 183 | + } |
| 184 | + |
| 185 | + // Check subdir/file4.txt found path1 |
| 186 | + { |
| 187 | + CanonPath f4Path("/subdir/file4.txt"); |
| 188 | + auto it = foundRefs.find(f4Path); |
| 189 | + ASSERT_TRUE(it != foundRefs.end()); |
| 190 | + EXPECT_EQ(it->second.size(), 1); |
| 191 | + EXPECT_TRUE(it->second.count(path1)); |
| 192 | + } |
| 193 | + |
| 194 | + // Check symlink found path2 |
| 195 | + { |
| 196 | + CanonPath linkPath("/link1"); |
| 197 | + auto it = foundRefs.find(linkPath); |
| 198 | + ASSERT_TRUE(it != foundRefs.end()); |
| 199 | + EXPECT_EQ(it->second.size(), 1); |
| 200 | + EXPECT_TRUE(it->second.count(path2)); |
| 201 | + } |
| 202 | + } |
| 203 | + |
| 204 | + // Test the map-based convenience API |
| 205 | + { |
| 206 | + auto results = scanForReferencesDeep(*accessor, CanonPath::root, refs); |
| 207 | + |
| 208 | + EXPECT_EQ(results.size(), 4); // file1, file2, file4, link1 |
| 209 | + |
| 210 | + // Verify all expected files are in the results |
| 211 | + EXPECT_TRUE(results.count(CanonPath("/file1.txt"))); |
| 212 | + EXPECT_TRUE(results.count(CanonPath("/file2.txt"))); |
| 213 | + EXPECT_TRUE(results.count(CanonPath("/subdir/file4.txt"))); |
| 214 | + EXPECT_TRUE(results.count(CanonPath("/link1"))); |
| 215 | + EXPECT_FALSE(results.count(CanonPath("/file3.txt"))); |
| 216 | + |
| 217 | + // Verify the references found in each file are correct |
| 218 | + EXPECT_EQ(results.at(CanonPath("/file1.txt")), StorePathSet{path1}); |
| 219 | + EXPECT_EQ(results.at(CanonPath("/file2.txt")), StorePathSet({path2, path3})); |
| 220 | + EXPECT_EQ(results.at(CanonPath("/subdir/file4.txt")), StorePathSet{path1}); |
| 221 | + EXPECT_EQ(results.at(CanonPath("/link1")), StorePathSet{path2}); |
| 222 | + } |
| 223 | +} |
| 224 | + |
82 | 225 | } // namespace nix |
0 commit comments