|
1 | 1 | package protogen |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "context" |
| 5 | + "crypto/sha256" |
| 6 | + "encoding/hex" |
| 7 | + "io" |
4 | 8 | "os" |
5 | 9 | "path/filepath" |
6 | 10 | "slices" |
| 11 | + "strings" |
7 | 12 | "testing" |
8 | 13 | ) |
9 | 14 |
|
@@ -194,3 +199,115 @@ func TestHashProtoFilesPortableAndContentSensitive(t *testing.T) { |
194 | 199 | t.Fatalf("content hash must change when a proto source changes") |
195 | 200 | } |
196 | 201 | } |
| 202 | + |
| 203 | +func TestToolVersionChangeInvalidatesCachedPackage(t *testing.T) { |
| 204 | + dir, files := writeProtoTree(t, map[string]string{ |
| 205 | + "foo.proto": "syntax = \"proto3\";\npackage foo;\n", |
| 206 | + }) |
| 207 | + cache := NewCache() |
| 208 | + cache.ProtocFlagsHash = "flags" |
| 209 | + cache.ToolVersions = "starpc-python=old" |
| 210 | + if err := cache.UpdatePackage("example/foo", files, nil, dir); err != nil { |
| 211 | + t.Fatal(err) |
| 212 | + } |
| 213 | + |
| 214 | + stale, err := cache.NeedsRegeneration( |
| 215 | + "example/foo", files, dir, "flags", "starpc-python=old", false, |
| 216 | + ) |
| 217 | + if err != nil { |
| 218 | + t.Fatal(err) |
| 219 | + } |
| 220 | + if stale { |
| 221 | + t.Fatal("matching tool state invalidated cached output") |
| 222 | + } |
| 223 | + stale, err = cache.NeedsRegeneration( |
| 224 | + "example/foo", files, dir, "flags", "starpc-python=new", false, |
| 225 | + ) |
| 226 | + if err != nil { |
| 227 | + t.Fatal(err) |
| 228 | + } |
| 229 | + if !stale { |
| 230 | + t.Fatal("changed tool state reused cached output") |
| 231 | + } |
| 232 | +} |
| 233 | + |
| 234 | +func TestGetToolVersionsIncludesUVLock(t *testing.T) { |
| 235 | + dir := t.TempDir() |
| 236 | + lock := []byte("version = 1\nrevision = 3\n") |
| 237 | + if err := os.WriteFile(filepath.Join(dir, "uv.lock"), lock, 0o644); err != nil { |
| 238 | + t.Fatal(err) |
| 239 | + } |
| 240 | + generator := &Generator{ |
| 241 | + ProjectDir: dir, |
| 242 | + Config: NewConfig(), |
| 243 | + Plugins: &Plugins{StarpcPython: &Plugin{}}, |
| 244 | + } |
| 245 | + versions := generator.getToolVersions() |
| 246 | + want := sha256.Sum256(lock) |
| 247 | + if !strings.Contains(versions, "uv.lock="+hex.EncodeToString(want[:])) { |
| 248 | + t.Fatalf("tool versions omit uv.lock digest: %q", versions) |
| 249 | + } |
| 250 | +} |
| 251 | + |
| 252 | +func TestGetToolVersionsIgnoresUVLockWithoutStarpcPython(t *testing.T) { |
| 253 | + dir := t.TempDir() |
| 254 | + if err := os.WriteFile(filepath.Join(dir, "uv.lock"), []byte("changed"), 0o644); err != nil { |
| 255 | + t.Fatal(err) |
| 256 | + } |
| 257 | + generator := &Generator{ |
| 258 | + ProjectDir: dir, |
| 259 | + Config: NewConfig(), |
| 260 | + Plugins: &Plugins{}, |
| 261 | + } |
| 262 | + if versions := generator.getToolVersions(); strings.Contains(versions, "uv.lock=") { |
| 263 | + t.Fatalf("unselected Python plugin invalidated tool state: %q", versions) |
| 264 | + } |
| 265 | +} |
| 266 | + |
| 267 | +func TestGenerateFailureDoesNotPersistToolVersions(t *testing.T) { |
| 268 | + dir := t.TempDir() |
| 269 | + vendorDir := filepath.Join(dir, "vendor") |
| 270 | + if err := os.MkdirAll(vendorDir, 0o755); err != nil { |
| 271 | + t.Fatal(err) |
| 272 | + } |
| 273 | + if err := os.WriteFile(filepath.Join(dir, "bad.proto"), []byte("not protobuf"), 0o644); err != nil { |
| 274 | + t.Fatal(err) |
| 275 | + } |
| 276 | + if err := os.WriteFile(filepath.Join(dir, "uv.lock"), []byte("changed plugin"), 0o644); err != nil { |
| 277 | + t.Fatal(err) |
| 278 | + } |
| 279 | + config := NewConfig() |
| 280 | + config.ProjectDir = dir |
| 281 | + config.Targets = []string{"bad.proto"} |
| 282 | + cache := NewCache() |
| 283 | + cache.ToolVersions = "accepted" |
| 284 | + plugins := &Plugins{ |
| 285 | + Languages: Languages{LanguagePython: {}}, |
| 286 | + RPCLibraries: RPCLibraries{RPCLibraryStarpcPython: {}}, |
| 287 | + StarpcPython: &Plugin{ |
| 288 | + Name: "starpc-python", |
| 289 | + BinaryName: "protoc-gen-starpc-python", |
| 290 | + Path: "/bin/false", |
| 291 | + Type: PluginTypePython, |
| 292 | + OutFlag: "starpc-python_out", |
| 293 | + }, |
| 294 | + } |
| 295 | + generator := &Generator{ |
| 296 | + Config: config, |
| 297 | + Plugins: plugins, |
| 298 | + Cache: cache, |
| 299 | + ProjectDir: dir, |
| 300 | + ModuleDir: dir, |
| 301 | + ModulePath: "example.com/project", |
| 302 | + VendorDir: vendorDir, |
| 303 | + OutDir: vendorDir, |
| 304 | + Stdout: io.Discard, |
| 305 | + Stderr: io.Discard, |
| 306 | + } |
| 307 | + if err := generator.Generate(context.Background()); err == nil { |
| 308 | + t.Fatal("invalid source generation unexpectedly succeeded") |
| 309 | + } |
| 310 | + if cache.ToolVersions != "accepted" { |
| 311 | + t.Fatalf("failed generation persisted tool state %q", cache.ToolVersions) |
| 312 | + } |
| 313 | +} |
0 commit comments