Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commita61b8bc

Browse files
authored
test: add golden file test for ConvertState (#20832)
Refactoring `ConvertState` is something we should eventually do. This PRadds a golden file unit test for the output of `ConvertState` (evenerrors).That way if a refactor occurs, we can verify the output is unchanged forour test cases.
1 parente0a32e0 commita61b8bc

File tree

57 files changed

+3160
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+3160
-0
lines changed

‎provisioner/terraform/resources_test.go‎

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@ package terraform_test
22

33
import (
44
"context"
5+
"crypto/sha256"
56
"encoding/json"
67
"fmt"
78
"os"
89
"path/filepath"
910
"runtime"
11+
"slices"
1012
"sort"
1113
"strings"
1214
"testing"
1315

1416
"github.com/google/go-cmp/cmp"
17+
"github.com/google/uuid"
1518
tfjson"github.com/hashicorp/terraform-json"
1619
"github.com/stretchr/testify/require"
1720
protobuf"google.golang.org/protobuf/proto"
@@ -30,6 +33,115 @@ func ctxAndLogger(t *testing.T) (context.Context, slog.Logger) {
3033
returncontext.Background(),testutil.Logger(t)
3134
}
3235

36+
// TestConvertStateGolden compares the output of ConvertState to a golden
37+
// file to prevent regressions. If the logic changes, update the golden files
38+
// accordingly.
39+
//
40+
// This was created to aid in refactoring `ConvertState`.
41+
funcTestConvertStateGolden(t*testing.T) {
42+
t.Parallel()
43+
44+
testResourceDirectories:=filepath.Join("testdata","resources")
45+
entries,err:=os.ReadDir(testResourceDirectories)
46+
require.NoError(t,err)
47+
48+
for_,testDirectory:=rangeentries {
49+
if!testDirectory.IsDir() {
50+
continue
51+
}
52+
53+
testFiles,err:=os.ReadDir(filepath.Join(testResourceDirectories,testDirectory.Name()))
54+
require.NoError(t,err)
55+
56+
// ConvertState works on both a plan file and a state file.
57+
// The test should create a golden file for both.
58+
for_,step:=range []string{"plan","state"} {
59+
srcIdc:=slices.IndexFunc(testFiles,func(entry os.DirEntry)bool {
60+
returnstrings.HasSuffix(entry.Name(),fmt.Sprintf(".tf%s.json",step))
61+
})
62+
dotIdx:=slices.IndexFunc(testFiles,func(entry os.DirEntry)bool {
63+
returnstrings.HasSuffix(entry.Name(),fmt.Sprintf(".tf%s.dot",step))
64+
})
65+
66+
// If the directory is missing these files, we cannot run ConvertState
67+
// on it. So it's skipped.
68+
ifsrcIdc==-1||dotIdx==-1 {
69+
continue
70+
}
71+
72+
t.Run(step+"_"+testDirectory.Name(),func(t*testing.T) {
73+
t.Parallel()
74+
testDirectoryPath:=filepath.Join(testResourceDirectories,testDirectory.Name())
75+
planFile:=filepath.Join(testDirectoryPath,testFiles[srcIdc].Name())
76+
dotFile:=filepath.Join(testDirectoryPath,testFiles[dotIdx].Name())
77+
78+
ctx:=testutil.Context(t,testutil.WaitMedium)
79+
logger:=slogtest.Make(t,nil)
80+
81+
// Gather plan
82+
tfStepRaw,err:=os.ReadFile(planFile)
83+
require.NoError(t,err)
84+
85+
varmodules []*tfjson.StateModule
86+
switchstep {
87+
case"plan":
88+
vartfPlan tfjson.Plan
89+
err=json.Unmarshal(tfStepRaw,&tfPlan)
90+
require.NoError(t,err)
91+
92+
modules= []*tfjson.StateModule{tfPlan.PlannedValues.RootModule}
93+
iftfPlan.PriorState!=nil {
94+
modules=append(modules,tfPlan.PriorState.Values.RootModule)
95+
}
96+
case"state":
97+
vartfState tfjson.State
98+
err=json.Unmarshal(tfStepRaw,&tfState)
99+
require.NoError(t,err)
100+
modules= []*tfjson.StateModule{tfState.Values.RootModule}
101+
default:
102+
t.Fatalf("unknown step: %s",step)
103+
}
104+
105+
// Gather graph
106+
dotFileRaw,err:=os.ReadFile(dotFile)
107+
require.NoError(t,err)
108+
109+
// expectedOutput is `any` to support errors too. If `ConvertState` returns an
110+
// error, that error is the golden file output.
111+
varexpectedOutputany
112+
state,err:=terraform.ConvertState(ctx,modules,string(dotFileRaw),logger)
113+
iferr==nil {
114+
sortResources(state.Resources)
115+
sortExternalAuthProviders(state.ExternalAuthProviders)
116+
deterministicAppIDs(state.Resources)
117+
expectedOutput=state
118+
}else {
119+
// Write the error to the file then. Track errors as much as valid paths.
120+
expectedOutput=err.Error()
121+
}
122+
123+
expPath:=filepath.Join(testDirectoryPath,fmt.Sprintf("converted_state.%s.golden",step))
124+
if*updateGoldenFiles {
125+
gotBytes,err:=json.MarshalIndent(expectedOutput,""," ")
126+
require.NoError(t,err,"marshaling converted state to JSON")
127+
// Newline at end of file for git purposes
128+
err=os.WriteFile(expPath,append(gotBytes,'\n'),0o600)
129+
require.NoError(t,err)
130+
return
131+
}
132+
133+
gotBytes,err:=json.Marshal(expectedOutput)
134+
require.NoError(t,err,"marshaling converted state to JSON")
135+
136+
expBytes,err:=os.ReadFile(expPath)
137+
require.NoError(t,err)
138+
139+
require.JSONEq(t,string(expBytes),string(gotBytes),"converted state")
140+
})
141+
}
142+
}
143+
}
144+
33145
funcTestConvertResources(t*testing.T) {
34146
t.Parallel()
35147
// nolint:dogsled
@@ -1669,3 +1781,18 @@ func sortExternalAuthProviders(providers []*proto.ExternalAuthProviderResource)
16691781
returnstrings.Compare(providers[i].Id,providers[j].Id)==-1
16701782
})
16711783
}
1784+
1785+
// deterministicAppIDs handles setting agent app ids to something deterministic.
1786+
// In plan files, ids are not present. In state files, they are.
1787+
// It is simpler for comparisons if we just set it to something deterministic.
1788+
funcdeterministicAppIDs(resources []*proto.Resource) {
1789+
for_,resource:=rangeresources {
1790+
for_,agent:=rangeresource.Agents {
1791+
for_,app:=rangeagent.Apps {
1792+
data:=sha256.Sum256([]byte(app.Slug+app.DisplayName))
1793+
id,_:=uuid.FromBytes(data[:16])
1794+
app.Id=id.String()
1795+
}
1796+
}
1797+
}
1798+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"Resources": [
3+
{
4+
"name": "a",
5+
"type": "coder_ai_task"
6+
}
7+
],
8+
"Parameters": [],
9+
"Presets": [],
10+
"ExternalAuthProviders": [],
11+
"AITasks": [
12+
{
13+
"sidebar_app": {
14+
"id": "5ece4674-dd35-4f16-88c8-82e40e72e2fd"
15+
},
16+
"app_id": "5ece4674-dd35-4f16-88c8-82e40e72e2fd"
17+
}
18+
],
19+
"HasAITasks": true,
20+
"HasExternalAgents": false
21+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"Resources": [
3+
{
4+
"name": "a",
5+
"type": "coder_ai_task"
6+
}
7+
],
8+
"Parameters": [],
9+
"Presets": [],
10+
"ExternalAuthProviders": [],
11+
"AITasks": [
12+
{
13+
"id": "c4f032b8-97e4-42b0-aa2f-30a9e698f8d4",
14+
"sidebar_app": {
15+
"id": "5ece4674-dd35-4f16-88c8-82e40e72e2fd"
16+
},
17+
"app_id": "5ece4674-dd35-4f16-88c8-82e40e72e2fd"
18+
}
19+
],
20+
"HasAITasks": true,
21+
"HasExternalAgents": false
22+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"Resources": [
3+
{
4+
"name": "a",
5+
"type": "coder_ai_task"
6+
},
7+
{
8+
"name": "b",
9+
"type": "coder_ai_task"
10+
}
11+
],
12+
"Parameters": [],
13+
"Presets": [],
14+
"ExternalAuthProviders": [],
15+
"AITasks": [
16+
{
17+
"sidebar_app": {
18+
"id": "5ece4674-dd35-4f16-88c8-82e40e72e2fd"
19+
},
20+
"app_id": "5ece4674-dd35-4f16-88c8-82e40e72e2fd"
21+
},
22+
{
23+
"sidebar_app": {
24+
"id": "5ece4674-dd35-4f16-88c8-82e40e72e2fd"
25+
},
26+
"app_id": "5ece4674-dd35-4f16-88c8-82e40e72e2fd"
27+
}
28+
],
29+
"HasAITasks": true,
30+
"HasExternalAgents": false
31+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"Resources": [
3+
{
4+
"name": "a",
5+
"type": "coder_ai_task"
6+
},
7+
{
8+
"name": "b",
9+
"type": "coder_ai_task"
10+
}
11+
],
12+
"Parameters": [],
13+
"Presets": [],
14+
"ExternalAuthProviders": [],
15+
"AITasks": [
16+
{
17+
"id": "89e6ab36-2e98-4d13-9b4c-69b7588b7e1d",
18+
"sidebar_app": {
19+
"id": "5ece4674-dd35-4f16-88c8-82e40e72e2fd"
20+
},
21+
"app_id": "5ece4674-dd35-4f16-88c8-82e40e72e2fd"
22+
},
23+
{
24+
"id": "5ece4674-dd35-4f16-88c8-82e40e72e2fd",
25+
"sidebar_app": {
26+
"id": "5ece4674-dd35-4f16-88c8-82e40e72e2fd"
27+
},
28+
"app_id": "5ece4674-dd35-4f16-88c8-82e40e72e2fd"
29+
}
30+
],
31+
"HasAITasks": true,
32+
"HasExternalAgents": false
33+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"Resources": [
3+
{
4+
"name": "a",
5+
"type": "coder_ai_task"
6+
}
7+
],
8+
"Parameters": [],
9+
"Presets": [],
10+
"ExternalAuthProviders": [],
11+
"AITasks": [
12+
{
13+
"sidebar_app": {
14+
"id": "5ece4674-dd35-4f16-88c8-82e40e72e2fd"
15+
},
16+
"app_id": "5ece4674-dd35-4f16-88c8-82e40e72e2fd"
17+
}
18+
],
19+
"HasAITasks": true,
20+
"HasExternalAgents": false
21+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"Resources": [
3+
{
4+
"name": "a",
5+
"type": "coder_ai_task"
6+
}
7+
],
8+
"Parameters": [],
9+
"Presets": [],
10+
"ExternalAuthProviders": [],
11+
"AITasks": [
12+
{
13+
"id": "89e6ab36-2e98-4d13-9b4c-69b7588b7e1d",
14+
"sidebar_app": {
15+
"id": "5ece4674-dd35-4f16-88c8-82e40e72e2fd"
16+
},
17+
"app_id": "5ece4674-dd35-4f16-88c8-82e40e72e2fd"
18+
}
19+
],
20+
"HasAITasks": true,
21+
"HasExternalAgents": false
22+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"Resources": [
3+
{
4+
"name": "example",
5+
"type": "null_resource",
6+
"agents": [
7+
{
8+
"name": "main",
9+
"operating_system": "linux",
10+
"architecture": "amd64",
11+
"Auth": {
12+
"Token": ""
13+
},
14+
"connection_timeout_seconds": 120,
15+
"display_apps": {
16+
"vscode": true,
17+
"web_terminal": true,
18+
"ssh_helper": true,
19+
"port_forwarding_helper": true
20+
},
21+
"resources_monitoring": {},
22+
"api_key_scope": "all"
23+
}
24+
],
25+
"module_path": "module.module"
26+
}
27+
],
28+
"Parameters": [],
29+
"Presets": [],
30+
"ExternalAuthProviders": [],
31+
"AITasks": [],
32+
"HasAITasks": false,
33+
"HasExternalAgents": false
34+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"Resources": [
3+
{
4+
"name": "example",
5+
"type": "null_resource",
6+
"agents": [
7+
{
8+
"id": "8cb7c83a-eddb-45e9-a78c-4b50d0f10e5e",
9+
"name": "main",
10+
"operating_system": "linux",
11+
"architecture": "amd64",
12+
"Auth": {
13+
"Token": "59bcf169-14fe-497d-9a97-709c1d837848"
14+
},
15+
"connection_timeout_seconds": 120,
16+
"display_apps": {
17+
"vscode": true,
18+
"web_terminal": true,
19+
"ssh_helper": true,
20+
"port_forwarding_helper": true
21+
},
22+
"resources_monitoring": {},
23+
"api_key_scope": "all"
24+
}
25+
],
26+
"module_path": "module.module"
27+
}
28+
],
29+
"Parameters": [],
30+
"Presets": [],
31+
"ExternalAuthProviders": [],
32+
"AITasks": [],
33+
"HasAITasks": false,
34+
"HasExternalAgents": false
35+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp