From 71b43897d6f719295ce5b17bff96fe3f9de104f2 Mon Sep 17 00:00:00 2001 From: Charlie Voiselle <464492+angrycub@users.noreply.github.com> Date: Thu, 27 Mar 2025 19:04:07 -0400 Subject: [PATCH] Add a test fixture --- CLAUDE.md | 19 +++++++++++++ devcontainer/devcontainer_test.go | 20 ++++++++++++++ devcontainer/features/features_test.go | 26 ++++++++++++++++++ .../devcontainer-feature-hello.tgz | Bin 0 -> 1093 bytes integration/integration_test.go | 25 +++++++++++++++++ 5 files changed, 90 insertions(+) create mode 100644 CLAUDE.md create mode 100644 devcontainer/features/testdata/feature-tarball/devcontainer-feature-hello.tgz diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..cdd94f7f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,19 @@ +# Envbuilder Development Guide + +## Build/Test/Lint Commands +- Build: `make build` +- Run tests: `go test -count=1 ./...` +- Run tests with race detection: `go test -race -count=3 ./...` +- Run a single test: `go test -v -count=1 ./path/to/package -run TestName` +- Lint Go code: `make lint/go` +- Lint shell scripts: `make lint/shellcheck` + +## Code Style Guidelines +- Imports: Standard Go import grouping (stdlib, external, internal) +- Error handling: Return errors with context using `fmt.Errorf("operation: %w", err)` +- Logging: Use the provided logger (`options.Logger`) with appropriate log levels +- Testing: Use testify for assertions and test setup +- Types: Strong typing with proper interfaces, use Go 1.22+ features +- Naming: Follow Go conventions (camelCase for unexported, PascalCase for exported) +- Documentation: Write clear godoc comments for exported functions and types +- File organization: Keep related functionality in the same package \ No newline at end of file diff --git a/devcontainer/devcontainer_test.go b/devcontainer/devcontainer_test.go index d304e763..f7daf1da 100644 --- a/devcontainer/devcontainer_test.go +++ b/devcontainer/devcontainer_test.go @@ -43,6 +43,26 @@ func TestParse(t *testing.T) { require.Equal(t, "Dockerfile", parsed.Build.Dockerfile) } +func TestParseDirectTarball(t *testing.T) { + t.Parallel() + raw := `{ + "build": { + "dockerfile": "Dockerfile", + "context": ".", + }, + // Comments here! + "image": "codercom/code-server:latest", + "features": { + "https://github.com/coder/envbuilder/raw/94e6c14f5252ae47fcaf0886e6f3f7db91a75ce8/devcontainer/features/testdata/feature-tarball/devcontainer-feature-hello.tgz": { + "greeting": "Hello" + } + } +}` + parsed, err := devcontainer.Parse([]byte(raw)) + require.NoError(t, err) + require.Equal(t, "Dockerfile", parsed.Build.Dockerfile) +} + func TestCompileWithFeatures(t *testing.T) { t.Parallel() registry := registrytest.New(t) diff --git a/devcontainer/features/features_test.go b/devcontainer/features/features_test.go index 389193c6..5d170f17 100644 --- a/devcontainer/features/features_test.go +++ b/devcontainer/features/features_test.go @@ -51,6 +51,25 @@ func TestExtract(t *testing.T) { _, err := features.Extract(fs, "", "/", ref) require.ErrorContains(t, err, "id is required") }) + + t.Run("LoadTarballArtifacts", func(t *testing.T) { + t.Run("Success", func(t *testing.T) { + t.Parallel() + ref := "https://127.0.0.1:12345/devcontainer-feature-hello.tgz" + fs := memfs.New() + _, err := features.Extract(fs, "", "/", ref) + require.NoError(t, err) + }) + t.Run("InvalidName", func(t *testing.T) { + t.Parallel() + + ref := "https://127.0.0.1:12345/invalid-name-hello.tgz" + + fs := memfs.New() + _, err := features.Extract(fs, "", "/", ref) + require.ErrorContains(t, err, "invalid name") + }) + }) t.Run("Success", func(t *testing.T) { t.Parallel() registry := registrytest.New(t) @@ -117,4 +136,11 @@ func TestCompile(t *testing.T) { require.Equal(t, "FROM scratch AS envbuilder_feature_test\nCOPY --from=coder/test:latest / /", strings.TrimSpace(fromDirective)) require.Equal(t, "WORKDIR /.envbuilder/features/test\nRUN --mount=type=bind,from=envbuilder_feature_test,target=/.envbuilder/features/test,rw _CONTAINER_USER=\"containerUser\" _REMOTE_USER=\"remoteUser\" ./install.sh", strings.TrimSpace(runDirective)) }) + // t.Run("Tarball", func(t *testing.T) { + // t.Parallel() + // spec := &features.Spec{} + // _, directive, err := spec.Compile("https://127.0.0.1:12345/devcontainer-feature-hello.tgz", "hello", "/", "containerUser", "remoteUser", false, nil) + // require.NoError(t, err) + // require.Equal(t, "WORKDIR /\nRUN _CONTAINER_USER=\"containerUser\" _REMOTE_USER=\"remoteUser\" ./install.sh", strings.TrimSpace(directive)) + // }) } diff --git a/devcontainer/features/testdata/feature-tarball/devcontainer-feature-hello.tgz b/devcontainer/features/testdata/feature-tarball/devcontainer-feature-hello.tgz new file mode 100644 index 0000000000000000000000000000000000000000..2ef02a6b68591cc2751ba7b631999e43e21b45dc GIT binary patch literal 1093 zcmV-L1iJeliwFR#sO4t>1MOFBPuoZk?&tj#LxL305!(qg(dF8c5V$SU5hMuh2SBi~ z*YTd#-e`B75JCR?&DwE7QYcrcfGTA^5bwM^Gdo^CJDmFahsVB?gzsKXJkM+G?ScJm zx8CQ!Ub9$RX?X1hH1^u<#%^=B)oOs(*ljeuci_EXO>@b#p$bb0VyXmZPx_%-6#W3F z*bB^me-j~9_`-ycJMc{^5pKX%Y?s@F>_h$pW2=XVQIjb~NVN+7Elq}k!MUab)ZEA)QUl*%9ul*6SPvQZ#YLwU-Sc4vNd+j+y3o9F2?=3#m02tBz>QCShx zFl{QiZAzv#VP40{(4dDVp(B& z8X}Ip8&eH5GqOHl*c03!wphsoOw8coVY}+HZaKEcS6i_$L)~%R0XK2hcLJF_)7-w0 zeK$d)t5s06+IEZTqNz6bsj2B(~tDd(!jIkSlJSYx33ycP+m}o;(V_ zFvqj}-)ye%f2+0ET=V~H$c(5{nlMYXOLswKr!ex+nsVwWRS(#?;doB7)Q1b|wYEp^ zkkVHcoV`r@CA%(ZbBjS9x0nUP&OqkYp^`(XsjzStOQzh6Y`hXNQ!nT+4+ofbD?`F2t~nnLLP2)&nf|KsadyLfpdGOcHZW83nm zE4*Fb`b4hfHDz{s938)0j_3Hl)o!oO|E>4!HUGbcR5#qdoeg86nSnZv^DKxZGIwD3 zh~`7^j4z;;vt_M9$mdhv?;UkNf84w)A3OCdMeaj}38yoN9*(j|%Of7*a62EVx$eMb z8HHF?uzt10IjmXUQFM_kD?K}Z-;#c5yQG)r+eL#j2x@D%!!90^Gh<}3*Z{~S4Uyo64m&up3~6E-BT zHVqJJ95V#7@ivy-8jw-G#8%++j1YT>%ZnBA6q!01)mh&t}Mj(dIZ7FZN9`#x7U`Jgncq_%l|>L25Xe|>)D zpW=J|8GxFGSSD!a3O%Koe2;kj<>c76$LD$6@4AFY2PfU${!tfk{&#sxFi%yuj5{wf zpa%X1H`B@$GN2;=Op0mdl6?38{>dk@_;^nJ!;iH~Z+G{?`|Hm}QX}1ia-Gzm3S9{Y zQpob9TyEZB#B}lbOn2aNb88$+n(*xnG