Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ jobs:
go-version: '^1.21'

- run: go build -o temporal-features
- run: go test ./cmd
- run: go test ./...
working-directory: harness/go

- name: Get the latest release version
id: latest_version
Expand Down
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ Note, `go run .` can be used in place of `go build` + `temporal-features` to sav
`LANG` can be `go`, `java`, `ts`, `php`, `py`, `cs`, or `rb`. `VERSION` is per SDK and if left off, uses the latest version set for
the language in this repository.

`VERSION` may also be a path to a local checkout of the matching SDK repository. This is useful for sanity checking a
feature against unreleased SDK changes before cutting a release:

```bash
go run . run --lang go --version ../sdk-go --no-history-check worker_shutdown/poll_complete_on_shutdown
```

`PATTERN` must match either the features relative directory _or_ the relative directory + `/feature.<ext>` via
[Go path match rules](https://pkg.go.dev/path#Match) which notably does not include recursive depth matching. If
`PATTERN` arguments are not present, the default is to run all features.
Expand Down Expand Up @@ -121,6 +128,47 @@ settings are:

- `go`
- `minVersion` - Minimum version in Go this feature should be run in. The feature will be skipped in older versions.
- `runVariants` - Optional list of named ways to run the feature. If present, the runner executes the feature once per
variant. Each variant gets a fresh embedded dev server, namespace, and task queue.
- `name` - Required stable name for the variant. It is included in logs and summary output as
`feature/path#variant-name`.
- `dynamicConfig` - Optional map of Temporal dynamic config values to apply when starting the embedded dev server for
this variant. These values override `dockerfiles/dynamicconfig/docker.yaml` for this variant only.
- `expectNamespaceCapabilities` - Optional map of namespace capability field names to expected boolean values. The
runner checks these with `DescribeNamespace` after the variant's server starts and before the feature runs. Keys
must match `DescribeNamespace` capability field names. When set, the validated values are also available to the
feature process as JSON in `FEATURE_NAMESPACE_CAPABILITIES` for variant-specific assertions.

For example:

```json
{
"runVariants": [
{
"name": "feature-enabled",
"dynamicConfig": {
"frontend.someFeatureFlag": true
},
"expectNamespaceCapabilities": {
"someFeatureCapability": true
}
},
{
"name": "feature-disabled",
"dynamicConfig": {
"frontend.someFeatureFlag": false
},
"expectNamespaceCapabilities": {
"someFeatureCapability": false
}
}
]
}
```

Run variants require the runner to start the embedded dev server so it can apply each variant's dynamic config. They
cannot be used with `--server`, which points the runner at an already-running external server. When `--server` is used
without explicit feature patterns, the runner skips features with `runVariants`.

There are also files in the `history/` subdirectory which contain history files used during run. See the
"History Checking" and "Generating History" sections for more info.
Expand Down
55 changes: 55 additions & 0 deletions cmd/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package cmd

import (
"encoding/json"
"os"
"os/exec"
"strings"
)

func namespaceCapabilitiesEnv(capabilities map[string]bool) string {
if len(capabilities) == 0 {
return ""
}
capabilitiesJSON, _ := json.Marshal(capabilities)
return string(capabilitiesJSON)
}

// applyNamespaceCapabilitiesEnv adds validated namespace capabilities metadata
// to a subprocess.
func applyNamespaceCapabilitiesEnv(cmd *exec.Cmd, capabilitiesJSON string) {
if capabilitiesJSON == "" {
return
}
if len(cmd.Env) == 0 {
cmd.Env = os.Environ()
} else {
cmd.Env = append([]string(nil), cmd.Env...)
}

prefix := featureNamespaceCapabilitiesEnv + "="
filtered := cmd.Env[:0]
for _, entry := range cmd.Env {
if !strings.HasPrefix(entry, prefix) {
filtered = append(filtered, entry)
}
}
cmd.Env = append(filtered, prefix+capabilitiesJSON)
}

// setNamespaceCapabilitiesEnv temporarily adds validated namespace capabilities
// metadata for in-process feature runs.
func setNamespaceCapabilitiesEnv(capabilitiesJSON string) func() {
if capabilitiesJSON == "" {
return func() {}
}
oldValue, ok := os.LookupEnv(featureNamespaceCapabilitiesEnv)
_ = os.Setenv(featureNamespaceCapabilitiesEnv, capabilitiesJSON)
return func() {
if ok {
_ = os.Setenv(featureNamespaceCapabilitiesEnv, oldValue)
} else {
_ = os.Unsetenv(featureNamespaceCapabilitiesEnv)
}
}
}
Loading
Loading