Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,11 @@ jobs:
run: docker run --rm --privileged tonistiigi/binfmt:latest --install all
- name: Setup source policy
uses: ./.github/actions/setup-source-policy
- name: Expose GitHub tokens for caching
uses: crazy-max/ghaction-github-runtime@3cb05d89e1f492524af3d41a1c98c83bc3025124 # v3.1.0
- name: Run integration tests
run: |
env | grep GITHUB
set -ex
if [ -n "${TEST_SUITE}" ] && [ ! "${TEST_SUITE}" = "other" ]; then
run="-run=${TEST_SUITE}"
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/worker-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,15 @@ jobs:
name: Setup (merged)
run: |
set -ex -o pipefail
EXTRA_FLAGS="--set worker.cache-to=type=gha,scope=main.${{matrix.target}},mode=max --set worker.cache-from=type=gha,scope=${MAIN_CACHE_SCOPE}"
ref="${{github.ref}}"
branch="${ref#refs/heads/}"
branch_scope="${branch//\//-}" # Replace slashes with dashes
gha_scope="${branch_scope}.${{matrix.target}}"

EXTRA_FLAGS="--set worker.cache-to=type=gha,scope=${gha_scope},mode=max --set worker.cache-from=type=gha,scope=${MAIN_CACHE_SCOPE} --set worker.cache-from=type=gha,scope=${gha_scope}"
EXTRA_FALGS+=" --set worker.tags=ghcr.io/${IMAGE_REPO}:${branch_scope} --set cache-to=type=inline --cache-from=type=registry,ref=${IMAGE_REPO}:${branch_scope} --push"
echo "EXTRA_FLAGS=${EXTRA_FLAGS}" >> $GITHUB_ENV

- name: Setup QEMU
run: docker run --rm --privileged tonistiigi/binfmt:latest --install all
- name: Expose GitHub tokens for caching
Expand Down
34 changes: 30 additions & 4 deletions cmd/test2json2gha/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (
"math"
"os"
"path"
"regexp"
"strings"
"sync"
"time"

"github.com/Azure/dalec"
Expand Down Expand Up @@ -47,7 +49,29 @@ type outputStreamer struct {

func (h *outputStreamer) HandleEvent(te *TestEvent) error {
if te.Output != "" {
_, err := h.out.Write([]byte(te.Output))
if !githubActionsCommand().MatchString(te.Output) {
_, err := h.out.Write([]byte(te.Output))
return err
}
}
return nil
}

var githubActionsCommand = sync.OnceValue(func() *regexp.Regexp {
return regexp.MustCompile(`^::(error|warning|notice|add-mask)(?:\s+file=([^,:]+)(?:,line=(\d+))?)?::`)
})

type githubActionsCommandPassthrough struct {
out io.Writer
}

func (h *githubActionsCommandPassthrough) HandleEvent(te *TestEvent) error {
if te.Output != "" && githubActionsCommand().MatchString(te.Output) {
out := h.out
if out == nil {
out = os.Stderr
}
_, err := out.Write([]byte(te.Output))
return err
}
return nil
Expand Down Expand Up @@ -109,9 +133,11 @@ func (h *resultsHandler) HandleEvent(te *TestEvent) error {
tr.elapsed = te.Elapsed

if te.Output != "" {
_, err := tr.output.WriteString(te.Output)
if err != nil {
return errors.Wrap(err, "error collecting test event output")
if !githubActionsCommand().MatchString(te.Output) {
_, err := tr.output.WriteString(te.Output)
if err != nil {
return errors.Wrap(err, "error collecting test event output")
}
}
}

Expand Down
16 changes: 16 additions & 0 deletions cmd/test2json2gha/event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ const (
{"Time":"2025-06-04T09:51:34.185052-07:00","Action":"output","Package":"some_package","Test":"TestGenTimeout","Output":"exit status 2\n"}
{"Time":"2025-03-31T09:46:20.328007-07:00","Action":"output","Package":"some_package","Output":"FAIL\n"}
{"Time":"2025-03-31T09:46:20.328475-07:00","Action":"output","Package":"some_package","Output":"FAIL\tsome_package\t0.249s\n"}
{"Time": "2025-03-31T09:46:20.32851-07:00","Action":"output","Package":"some_package","Output":"::warning file=foo_test.go,line=38::hello warning\n"}
{"Time": "2025-03-31T09:46:20.32851-07:00","Action":"output","Package":"some_package","Output":"::notice::hello notice\n"}
{"Time":"2025-03-31T09:46:20.32851-07:00","Action":"fail","Package":"some_package","Elapsed":0.25}
`

Expand Down Expand Up @@ -122,6 +124,8 @@ exit status 2
testLogsAnnotation = " foo_test.go:42: some error\n foo_test.go:43: some fatal error\n"

testPackageName = "some_package"

ghaCommandsOutput = "::warning file=foo_test.go,line=38::hello warning\n::notice::hello notice\n"
)

func mockTestResults(t *testing.T) iter.Seq[*TestResult] {
Expand Down Expand Up @@ -275,3 +279,15 @@ func TestWriteLogs(t *testing.T) {
assert.Equal(t, string(content), string(output), "log file content does not match expected output")
}
}

func TestGithubActionsPassthrough(t *testing.T) {
var output strings.Builder
out := &githubActionsCommandPassthrough{out: &output}

for event := range readTestEvents(t) {
err := out.HandleEvent(event)
assert.NilError(t, err)
}

assert.Equal(t, output.String(), ghaCommandsOutput)
}
12 changes: 7 additions & 5 deletions cmd/test2json2gha/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ import (
)

type config struct {
slowThreshold time.Duration
modName string
verbose bool
stream bool
logDir string
slowThreshold time.Duration
modName string
verbose bool
stream bool
logDir string
ghaCommandsOut io.Writer
}

func main() {
Expand Down Expand Up @@ -107,6 +108,7 @@ func do(in io.Reader, out io.Writer, cfg config) (bool, error) {
handlers := []EventHandler{
results,
&anyFailed,
&githubActionsCommandPassthrough{out: cfg.ghaCommandsOut},
}

if cfg.stream {
Expand Down
67 changes: 42 additions & 25 deletions cmd/test2json2gha/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"time"

"gotest.tools/v3/assert"
"gotest.tools/v3/assert/cmp"
)

const (
Expand All @@ -28,11 +29,13 @@ func TestDo(t *testing.T) {
input := strings.NewReader(testEventJSON)
var output bytes.Buffer

ghaBuff := bytes.NewBuffer(nil)
cfg := config{
slowThreshold: 200 * time.Millisecond,
modName: testModuleName,
verbose: false,
stream: false,
slowThreshold: 200 * time.Millisecond,
modName: testModuleName,
verbose: false,
stream: false,
ghaCommandsOut: ghaBuff,
}

anyFail, err := do(input, &output, cfg)
Expand All @@ -41,18 +44,21 @@ func TestDo(t *testing.T) {

// Non-verbose output should only include the grouped fail events + error annotations
expected := failGroup + timeoutGroup + annotation
assert.Equal(t, expected, output.String())
assert.Check(t, cmp.Equal(expected, output.String()))
assert.Check(t, cmp.Equal(ghaCommandsOutput, ghaBuff.String()))
})

t.Run("verbose=true", func(t *testing.T) {
input := strings.NewReader(testEventJSON)
var output bytes.Buffer

ghaBuff := bytes.NewBuffer(nil)
cfg := config{
slowThreshold: 200 * time.Millisecond,
modName: testModuleName,
verbose: true,
stream: false,
slowThreshold: 200 * time.Millisecond,
modName: testModuleName,
verbose: true,
stream: false,
ghaCommandsOut: ghaBuff,
}

anyFail, err := do(input, &output, cfg)
Expand All @@ -62,17 +68,21 @@ func TestDo(t *testing.T) {
// verbose output should include grouped events for all test results + error annotations
expected := failGroup + passGroup + skipGroup + timeoutGroup + annotation
assert.Equal(t, output.String(), expected)
assert.Check(t, cmp.Equal(expected, output.String()))
assert.Check(t, cmp.Equal(ghaCommandsOutput, ghaBuff.String()))
})

t.Run("stream=true", func(t *testing.T) {
input := strings.NewReader(testEventJSON)
var output bytes.Buffer

ghaBuff := bytes.NewBuffer(nil)
cfg := config{
slowThreshold: 200 * time.Millisecond,
modName: testModuleName,
verbose: false,
stream: true,
slowThreshold: 200 * time.Millisecond,
modName: testModuleName,
verbose: false,
stream: true,
ghaCommandsOut: ghaBuff,
}

anyFail, err := do(input, &output, cfg)
Expand All @@ -81,7 +91,8 @@ func TestDo(t *testing.T) {

// Stream output should include all the raw events + the grouped fail events + the annotation
expected := testEventPassOutput + testEventFailOutput + testEventSkipOutput + testEventTimeoutOutput + testEventPackageOutput + failGroup + timeoutGroup + annotation
assert.Equal(t, output.String(), expected)
assert.Check(t, cmp.Equal(expected, output.String()))
assert.Check(t, cmp.Equal(ghaCommandsOutput, ghaBuff.String()))
})

t.Run("summary", func(t *testing.T) {
Expand All @@ -94,11 +105,13 @@ func TestDo(t *testing.T) {

input := strings.NewReader(testEventJSON)

ghaBuff := bytes.NewBuffer(nil)
cfg := config{
slowThreshold: 200 * time.Millisecond,
modName: testModuleName,
verbose: false,
stream: false,
slowThreshold: 200 * time.Millisecond,
modName: testModuleName,
verbose: false,
stream: false,
ghaCommandsOut: ghaBuff,
}

anyFail, err := do(input, io.Discard, cfg)
Expand All @@ -125,20 +138,23 @@ some_package: 0.250s

`

assert.Equal(t, string(output), expect)
assert.Check(t, cmp.Equal(string(output), expect))
assert.Check(t, cmp.Equal(ghaCommandsOutput, ghaBuff.String()))
})

t.Run("LogDir", func(t *testing.T) {
input := strings.NewReader(testEventJSON)
var output bytes.Buffer

logDir := t.TempDir()
ghaBuff := bytes.NewBuffer(nil)
cfg := config{
slowThreshold: 500,
modName: "github.com/Azure/dalec/cmd/test2json2gha",
verbose: false,
stream: false,
logDir: logDir,
slowThreshold: 500,
modName: "github.com/Azure/dalec/cmd/test2json2gha",
verbose: false,
stream: false,
logDir: logDir,
ghaCommandsOut: ghaBuff,
}

anyFail, err := do(input, &output, cfg)
Expand All @@ -148,6 +164,7 @@ some_package: 0.250s
// Validate that logs are written to the specified directory
entries, err := os.ReadDir(logDir)
assert.NilError(t, err)
assert.Assert(t, len(entries) > 0, "expected log files to be written to the log directory")
assert.Check(t, len(entries) > 0, "expected log files to be written to the log directory")
assert.Check(t, cmp.Equal(ghaCommandsOutput, ghaBuff.String()))
})
}
18 changes: 18 additions & 0 deletions docker-bake.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ target "runc-azlinux" {
tags = tgt == "container" ? ["runc:mariner2"] : []
// only output non-container targets to the fs
output = tgt != "container" ? ["_output"] : []
cache-from = [
{
type = "gha"
scope = "main.${distro}/worker"
}
]
}

target "runc-jammy" {
Expand All @@ -101,6 +107,12 @@ target "runc-jammy" {
tags = tgt == "container" ? ["runc:jammy"] : []
// only output non-container targets to the fs
output = tgt != "container" ? ["_output"] : []
cache-from = [
{
type = "gha"
scope = "main.jammy/worker"
}
]
}

target "runc-test" {
Expand Down Expand Up @@ -176,6 +188,12 @@ dependencies:
}
target = "${distro}/container/depsonly"
tags = ["local/dalec/deps-only:${distro}"]
cache-from = [
{
type = "gha"
scope = "main.${distro}/worker"
}
]
}

target "test-deps-only" {
Expand Down
2 changes: 2 additions & 0 deletions test/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/trace"

_ "github.com/Azure/dalec/targets/plugin"
)

var (
Expand Down
Loading