TestResolveMatrixRefsDoesNotMutateInput is a regression test for #2890. The *ast.Matrix passed to resolveMatrixRefs is part of the shared, cached Task AST: the same *ast.Matrix is reused on every invocation of a task. If resolveMatrixRefs resolved `ref:` rows in place, concurrent invocations of the
(t *testing.T)
| 19 | // The invariant that prevents this is that resolveMatrixRefs must resolve into |
| 20 | // a copy and leave its input untouched, which this test asserts deterministically. |
| 21 | func TestResolveMatrixRefsDoesNotMutateInput(t *testing.T) { |
| 22 | t.Parallel() |
| 23 | |
| 24 | matrix := ast.NewMatrix( |
| 25 | &ast.MatrixElement{Key: "ARCH", Value: &ast.MatrixRow{Ref: ".ARCH_VAR"}}, |
| 26 | ) |
| 27 | |
| 28 | vars := ast.NewVars() |
| 29 | vars.Set("ARCH_VAR", ast.Var{Value: []any{"amd64"}}) |
| 30 | cache := &templater.Cache{Vars: vars} |
| 31 | |
| 32 | resolved, err := resolveMatrixRefs(matrix, cache) |
| 33 | require.NoError(t, err) |
| 34 | |
| 35 | // The returned matrix has the ref resolved... |
| 36 | row, ok := resolved.Get("ARCH") |
| 37 | require.True(t, ok, "ARCH row missing from resolved matrix") |
| 38 | require.Equal(t, []any{"amd64"}, row.Value) |
| 39 | |
| 40 | // ...but the shared input matrix must be left untouched. |
| 41 | orig, ok := matrix.Get("ARCH") |
| 42 | require.True(t, ok, "ARCH row missing from input matrix") |
| 43 | require.Nil(t, orig.Value, "input matrix was mutated: Ref rows must be resolved into a copy") |
| 44 | require.Equal(t, ".ARCH_VAR", orig.Ref, "input matrix Ref was altered") |
| 45 | } |