Skip to content

Commit 20b23aa

Browse files
Run example tests from provider module (#2786)
Fixes #2788 Author example tests within the provider module so the provider and unit tests are executing in the same process. 1. Start the provider host from the test setup. 2. Grab the host port and pass to the ProgramTest PULUMI_DEBUG_PROVIDERS env. 3. Use a deferred, cancellable context to shut down the provider at the end of the test. Benefits: - Diagnose provider behaviour by easily starting program tests in debug mode within an IDE and breakpoint straight through to provider calls. - Generates test-coverage data on which parts of the provider are or aren't tested via program tests. - Shorten testing feedback loop by removing requirement to build SDKs and provider binary before re-running tests. Specifics: - Implicitly label non-e2e tests via build tags as `unit`. - Local runs via `make test_provider` exclude integration tests by default for speed. - CI runs all tests by default - All tests are runnable within a development environment Aside: remove unused schema string in provider setup. ## Long Term (inspirational) Plan 1. Move all program tests into provider module as YAML. 2. Build simple matrix testing to automatically convert YAML to each language and confirm correctness. 3. Use [HTTP session capture and replay](https://pkg.go.dev/github.com/google/go-replayers/httpreplay) to enable very fast testing of whole provider end-to-end without hitting the real backend for local development. 4. Run capture only on nightly job and run all PRs in replay mode by default. 5. Remove SDKs and leave only the single provider module to get rid of mono-repo layout (also fixing go.work oddities).
1 parent 131b049 commit 20b23aa

File tree

13 files changed

+237
-123
lines changed

13 files changed

+237
-123
lines changed

.github/workflows/build-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ jobs:
216216
run: make --touch codegen schema provider
217217

218218
- name: Test Provider Library
219-
run: make test_provider
219+
run: PROVIDER_TEST_TAGS=all make test_provider
220220

221221
- name: Upload coverage reports to Codecov
222222
uses: codecov/codecov-action@v3

Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,11 @@ arm2pulumi_coverage_report: .make/provider_mod_download provider/cmd/$(PROVIDER)
122122
(cd provider/pkg/arm2pulumi/internal/testdata && if [ ! -d azure-quickstart-templates ]; then git clone https://github.com/Azure/azure-quickstart-templates && cd azure-quickstart-templates && git checkout 3b2757465c2de537e333f5e2d1c3776c349b8483; fi)
123123
(cd provider && go test -v -tags=coverage -run TestQuickstartTemplateCoverage github.com/pulumi/pulumi-azure-native/v2/provider/pkg/arm2pulumi/internal/test)
124124

125+
# Use PROVIDER_TEST_TAGS=all to run all tests including examples integrate tests
126+
PROVIDER_TEST_TAGS ?= unit # Default to unit tests only for quick local feedback
125127
.PHONY: test_provider
126128
test_provider: .make/provider_mod_download .make/provider_prebuild provider/cmd/$(PROVIDER)/*.go $(PROVIDER_PKG)
127-
cd provider && go test -v -coverprofile="coverage.txt" -coverpkg=./... $(PROVIDER_PKGS)
129+
cd provider && go test -v --tags=$(PROVIDER_TEST_TAGS) -coverprofile="coverage.txt" -coverpkg=./... $(PROVIDER_PKGS)
128130

129131
.PHONY: lint_provider
130132
lint_provider: .make/provider_mod_download provider/cmd/$(PROVIDER)/*.go $(PROVIDER_PKG)

examples/api/Pulumi.yaml

Lines changed: 0 additions & 3 deletions
This file was deleted.

examples/api/index.ts

Lines changed: 0 additions & 71 deletions
This file was deleted.

examples/api/package.json

Lines changed: 0 additions & 10 deletions
This file was deleted.

examples/api/tsconfig.json

Lines changed: 0 additions & 22 deletions
This file was deleted.

examples/examples_nodejs_test.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,6 @@ import (
1212
"github.com/stretchr/testify/assert"
1313
)
1414

15-
func TestAccApiTs(t *testing.T) {
16-
skipIfShort(t)
17-
test := getJSBaseOptions(t).
18-
With(integration.ProgramTestOptions{
19-
Dir: filepath.Join(getCwd(t), "api"),
20-
})
21-
22-
integration.ProgramTest(t, &test)
23-
}
24-
2515
func TestAccAppServiceTs(t *testing.T) {
2616
skipIfShort(t)
2717
test := getJSBaseOptions(t).

provider/cmd/pulumi-resource-azure-native/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,5 @@ func unsafeStringToBytes(data string) []byte {
2929
}
3030

3131
func main() {
32-
provider.Serve(providerName, version.Version, unsafeStringToBytes(pulumiSchema), pulumiSchema, unsafeStringToBytes(azureApiResources))
32+
provider.Serve(providerName, version.Version, unsafeStringToBytes(pulumiSchema), unsafeStringToBytes(azureApiResources))
3333
}

provider/pkg/provider/provider.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,14 @@ type azureNativeProvider struct {
7373
config map[string]string
7474
schemaBytes []byte
7575
metadataBytes []byte
76-
schemaString string // optimization, this and schemaBytes should have same underlying repr
7776
fullPkgSpec *schema.PackageSpec
7877
fullResourceMap *resources.AzureAPIMetadata
7978
converter *resources.SdkShapeConverter
8079
customResources map[string]*resources.CustomResource
8180
rgLocationMap map[string]string
8281
}
8382

84-
func makeProvider(host *provider.HostClient, name, version string, schemaBytes []byte, schemaString string,
83+
func makeProvider(host *provider.HostClient, name, version string, schemaBytes []byte,
8584
azureAPIResourcesBytes []byte) (rpc.ResourceProviderServer, error) {
8685
autorest.Count429AsRetry = false
8786
// Creating a REST client, defaulting to Pulumi Partner ID until the Configure method is invoked.
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Copyright 2023, Pulumi Corporation. All rights reserved.
2+
3+
// Disable running if we've specifically selected unit tests to run as this is an integration test.
4+
// This is easier than having to remember to explicitly tag every unit test with `//go:build unit || all`.
5+
//go:build !unit
6+
7+
package provider
8+
9+
import (
10+
"context"
11+
"fmt"
12+
"os"
13+
"path/filepath"
14+
"testing"
15+
16+
"google.golang.org/grpc"
17+
18+
rp "github.com/pulumi/pulumi/pkg/v3/resource/provider"
19+
"github.com/pulumi/pulumi/pkg/v3/testing/integration"
20+
"github.com/pulumi/pulumi/sdk/v3/go/common/util/rpcutil"
21+
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
22+
)
23+
24+
func TestStorageBlob(t *testing.T) {
25+
runTestProgram(t, "storage-blob")
26+
}
27+
28+
func TestApi(t *testing.T) {
29+
runTestProgram(t, "api")
30+
}
31+
32+
// runTestProgram runs an example from ./examples/<initialDir>
33+
// Any editDirs are applied in order, and the program is run after each edit. e.g. ./examples/<editDir>
34+
func runTestProgram(t *testing.T, initialDir string, editDirs ...string) {
35+
if t.Skipped() {
36+
return
37+
}
38+
ctx, cancel := context.WithCancel(context.Background())
39+
defer cancel()
40+
port, err := startProvider(ctx)
41+
if err != nil {
42+
t.Fatal(err)
43+
}
44+
45+
opts, err := getTestOptions(initialDir, editDirs, port)
46+
if err != nil {
47+
t.Fatal(err)
48+
return
49+
}
50+
51+
integration.ProgramTest(t, opts)
52+
}
53+
54+
func getTestOptions(initialDir string, editDirs []string, port int) (*integration.ProgramTestOptions, error) {
55+
cwd, err := os.Getwd()
56+
if err != nil {
57+
return nil, err
58+
}
59+
azureLocation := getLocation()
60+
test := integration.ProgramTestOptions{
61+
Dir: filepath.Join(cwd, "test-programs", initialDir),
62+
ExpectRefreshChanges: true,
63+
Config: map[string]string{
64+
"azure-native:location": azureLocation,
65+
},
66+
Env: []string{
67+
fmt.Sprintf("PULUMI_DEBUG_PROVIDERS=azure-native:%d", port),
68+
},
69+
}
70+
for _, editDir := range editDirs {
71+
test.EditDirs = append(test.EditDirs, integration.EditDir{
72+
Dir: filepath.Join(cwd, "test-programs", editDir),
73+
})
74+
}
75+
return &test, nil
76+
}
77+
78+
// startProvider starts the provider in a goProc and returns the port it's listening on.
79+
// To shut down the provider, cancel the context.
80+
func startProvider(ctx context.Context) (int, error) {
81+
providerName := "azure-native"
82+
cancelChannel := make(chan bool)
83+
go func() {
84+
<-ctx.Done()
85+
close(cancelChannel)
86+
}()
87+
var host *rp.HostClient // We don't initialize this - to mimic attach mode
88+
version := ""
89+
schemaBytes, err := os.ReadFile(filepath.Join("..", "..", "..", "bin", "schema-full.json"))
90+
if err != nil {
91+
return 0, err
92+
}
93+
azureAPIResourcesBytes, err := os.ReadFile(filepath.Join("..", "..", "..", "bin", "metadata-compact.json"))
94+
if err != nil {
95+
return 0, err
96+
}
97+
handle, err := rpcutil.ServeWithOptions(rpcutil.ServeOptions{
98+
Cancel: cancelChannel,
99+
Init: func(srv *grpc.Server) error {
100+
prov, proverr := makeProvider(host, providerName, version, schemaBytes, azureAPIResourcesBytes)
101+
if proverr != nil {
102+
return fmt.Errorf("failed to create resource provider: %v", proverr)
103+
}
104+
pulumirpc.RegisterResourceProviderServer(srv, prov)
105+
return nil
106+
},
107+
Options: rpcutil.OpenTracingServerInterceptorOptions(nil),
108+
})
109+
if err != nil {
110+
return 0, fmt.Errorf("fatal: %v", err)
111+
}
112+
113+
return handle.Port, nil
114+
}
115+
116+
func getLocation() string {
117+
azureLocation := os.Getenv("ARM_LOCATION")
118+
if azureLocation == "" {
119+
azureLocation = "westus2"
120+
fmt.Println("Defaulting location to 'westus2'. You can override using the ARM_LOCATION variable.")
121+
}
122+
123+
return azureLocation
124+
}
125+
126+
func skipIfShort(t *testing.T) {
127+
if testing.Short() {
128+
t.Skip("skipping long-running test in short mode")
129+
}
130+
}

0 commit comments

Comments
 (0)