Skip to content

Commit

Permalink
move test summary to reusable code (solo-io#9969)
Browse files Browse the repository at this point in the history
Co-authored-by: soloio-bulldozer[bot] <48420018+soloio-bulldozer[bot]@users.noreply.github.com>
  • Loading branch information
sheidkamp and soloio-bulldozer[bot] authored Aug 29, 2024
1 parent be32b8f commit ba75c78
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 148 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ inputs:
istio-version:
required: true
description: The version of Istio to use
matrix-label:
required: true
description: The version of the matrix being used - used to name artifacts to prevent filename collisions

runs:
using: "composite"
Expand Down Expand Up @@ -48,5 +51,5 @@ runs:
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: go-test-summary-${{ inputs.cluster-name }}
name: go-test-summary-${{ inputs.cluster-name }}-${{ inputs.matrix-label }}
path: ./_test/test_log/go-test-summary
3 changes: 3 additions & 0 deletions .github/workflows/nightly-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ jobs:
test-args: ${{ matrix.test.go-test-args }}
run-regex: ${{ matrix.test.go-test-run-regex }}
istio-version: ${{ steps.dotenv.outputs.istio_version }}
matrix-label: ${{ matrix.version-files.label }}

end_to_end_tests_main:
name: End-to-End (branch=main, cluster=${{ matrix.test.cluster-name }}, version=${{ matrix.version-files.label }} )
Expand Down Expand Up @@ -171,6 +172,7 @@ jobs:
test-args: ${{ matrix.test.go-test-args }}
run-regex: ${{ matrix.test.go-test-run-regex }}
istio-version: ${{ steps.dotenv.outputs.istio_version }}
matrix-label: ${{ matrix.version-files.label }}

end_to_end_tests_17:
name: End-to-End (branch=v1.17.x, cluster=${{ matrix.test.cluster-name }}, version=${{ matrix.version-files.label }} )
Expand Down Expand Up @@ -231,6 +233,7 @@ jobs:
test-args: ${{ matrix.test.go-test-args }}
run-regex: ${{ matrix.test.go-test-run-regex }}
istio-version: ${{ steps.dotenv.outputs.istio_version }}
matrix-label: ${{ matrix.version-files.label }}

regression_tests_on_demand:
name: on demand regression tests
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pr-kubernetes-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,4 @@ jobs:
test-args: ${{ matrix.test.go-test-args }}
run-regex: ${{ matrix.test.go-test-run-regex }}
istio-version: ${{ steps.dotenv.outputs.istio_version }}
matrix-label: "pr"
6 changes: 6 additions & 0 deletions changelog/v1.18.0-beta18/test_summary_refactor.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
changelog:
- type: FIX
issueLink: https://github.com/solo-io/solo-projects/issues/6646
resolvesIssue: false
description: >-
Give each test summary artifact a unique name and refactor summary code into reusable package
149 changes: 4 additions & 145 deletions ci/github-actions/go-test-summary/go_test_summary.go
Original file line number Diff line number Diff line change
@@ -1,160 +1,19 @@
package main

import (
"bytes"
"encoding/json"
"flag"
"fmt"
"log"
"os"
"os/exec"
"strings"
"time"
)

// event is the JSON struct emitted by test2json.
// https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/cmd/internal/test2json/test2json.go
type event struct {
Time *time.Time `json:",omitempty"`
Action string `json:",omitempty"`
Package string `json:",omitempty"`
Test string `json:",omitempty"`
Elapsed float64 `json:",omitempty"`
Output string `json:",omitempty"`
}

// This tool is designed to read in a test log, process it through test2json, then
// parse through the output for pass, fail, or skip outcomes and log them all together.
"github.com/solo-io/gloo/ci/github-actions/go-test-summary/summary"
)

// This will also output a list of all leaf-node tests ran to produce the output.
// This can be used to determine if a new test was properly run or not.
func main() {
var logFile, outFile string
var jsonOutput bool

flag.StringVar(&logFile, "log-file", "./_test/test_log/go-test", "point to the raw string log output of go test command")
flag.StringVar(&outFile, "out-file", "./_test/test_log/go-test-summary", "where to place the output summary file")
flag.BoolVar(&jsonOutput, "json", false, "output as json")
flag.Parse()

b, err := readTestOutput(logFile)
if err != nil {
log.Fatal(err)
}

jsonb, err := testOutputToJson(b)
if err != nil {
log.Fatal(err)
}

allEvents, err := parseTestOutput(jsonb)
if err != nil {
log.Fatal(err)
}

resultEvents := selectResultEvents(allEvents)

leafNodeResults := selectLeafNodes(resultEvents)

output := printResults(leafNodeResults, jsonOutput)

writeResults(output, outFile)
}

func readTestOutput(fname string) ([]byte, error) {
f, err := os.ReadFile(fname)
if err != nil {
return nil, err
}
return f, nil
}

func testOutputToJson(in []byte) ([]byte, error) {
result := &bytes.Buffer{}

cmd := exec.Command("go", "tool", "test2json")
cmd.Env = os.Environ()
cmd.Stdin = bytes.NewBuffer(in)
cmd.Stdout = result

err := cmd.Run()
if err != nil {
return nil, err
}
return result.Bytes(), nil

}

func parseTestOutput(in []byte) ([]*event, error) {
rawEvents := bytes.Split(in, []byte{'\n'})
events := []*event{}
for _, rawEvent := range rawEvents {
if len(rawEvent) == 0 {
continue
}
ev := &event{}
if err := json.Unmarshal(rawEvent, ev); err != nil {
return nil, err
}
events = append(events, ev)
}
return events, nil
}

func selectResultEvents(allEvents []*event) []*event {
resultEvents := []*event{}
for _, ev := range allEvents {
if ev.Test != "" && (ev.Action == "pass" ||
ev.Action == "fail" ||
ev.Action == "skip") {
resultEvents = append(resultEvents, ev)
}
}
return resultEvents
}

func selectLeafNodes(events []*event) []*event {
t := &multitree{}
result := []*event{}

for _, ev := range events {
ev := ev
t.pushString(ev.Test, ev)
}

for _, n := range t.leafNodes() {
result = append(result, n.ev)
}

return result
}

func printResults(events []*event, jsonOutput bool) []byte {
if jsonOutput {
return printJson(events)
}
out := &bytes.Buffer{}
for _, ev := range events {
evStr := fmt.Sprintf("%s --- %s\n", strings.ToUpper(ev.Action), ev.Test)
out.WriteString(evStr)
}

b := out.Bytes()
os.Stdout.Write(b)
return b
}
func printJson(events []*event) []byte {
b, err := json.Marshal(events)
if err != nil {
log.Fatal(err)
}

os.Stdout.Write(b)

return b
}

func writeResults(output []byte, filename string) {
if err := os.WriteFile(filename, output, os.ModePerm); err != nil {
log.Fatal(err)
}
summary.Main(logFile, outFile, jsonOutput)
}
153 changes: 153 additions & 0 deletions ci/github-actions/go-test-summary/summary/go_test_summary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package summary

import (
"bytes"
"encoding/json"
"fmt"
"log"
"os"
"os/exec"
"strings"
"time"
)

// event is the JSON struct emitted by test2json.
// https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/cmd/internal/test2json/test2json.go
type event struct {
Time *time.Time `json:",omitempty"`
Action string `json:",omitempty"`
Package string `json:",omitempty"`
Test string `json:",omitempty"`
Elapsed float64 `json:",omitempty"`
Output string `json:",omitempty"`
}

// This tool is designed to read in a test log, process it through test2json, then
// parse through the output for pass, fail, or skip outcomes and log them all together.

// This will also output a list of all leaf-node tests ran to produce the output.
// This can be used to determine if a new test was properly run or not.
func Main(logFile, outFile string, jsonOutput bool) {

b, err := readTestOutput(logFile)
if err != nil {
log.Fatal(err)
}

jsonb, err := testOutputToJson(b)
if err != nil {
log.Fatal(err)
}

allEvents, err := parseTestOutput(jsonb)
if err != nil {
log.Fatal(err)
}

resultEvents := selectResultEvents(allEvents)

leafNodeResults := selectLeafNodes(resultEvents)

output := printResults(leafNodeResults, jsonOutput)

writeResults(output, outFile)
}

func readTestOutput(fname string) ([]byte, error) {
f, err := os.ReadFile(fname)
if err != nil {
return nil, err
}
return f, nil
}

func testOutputToJson(in []byte) ([]byte, error) {
result := &bytes.Buffer{}

cmd := exec.Command("go", "tool", "test2json")
cmd.Env = os.Environ()
cmd.Stdin = bytes.NewBuffer(in)
cmd.Stdout = result

err := cmd.Run()
if err != nil {
return nil, err
}
return result.Bytes(), nil

}

func parseTestOutput(in []byte) ([]*event, error) {
rawEvents := bytes.Split(in, []byte{'\n'})
events := []*event{}
for _, rawEvent := range rawEvents {
if len(rawEvent) == 0 {
continue
}
ev := &event{}
if err := json.Unmarshal(rawEvent, ev); err != nil {
return nil, err
}
events = append(events, ev)
}
return events, nil
}

func selectResultEvents(allEvents []*event) []*event {
resultEvents := []*event{}
for _, ev := range allEvents {
if ev.Test != "" && (ev.Action == "pass" ||
ev.Action == "fail" ||
ev.Action == "skip") {
resultEvents = append(resultEvents, ev)
}
}
return resultEvents
}

func selectLeafNodes(events []*event) []*event {
t := &multitree{}
result := []*event{}

for _, ev := range events {
ev := ev
t.pushString(ev.Test, ev)
}

for _, n := range t.leafNodes() {
result = append(result, n.ev)
}

return result
}

func printResults(events []*event, jsonOutput bool) []byte {
if jsonOutput {
return printJson(events)
}
out := &bytes.Buffer{}
for _, ev := range events {
evStr := fmt.Sprintf("%s --- %s\n", strings.ToUpper(ev.Action), ev.Test)
out.WriteString(evStr)
}

b := out.Bytes()
os.Stdout.Write(b)
return b
}
func printJson(events []*event) []byte {
b, err := json.Marshal(events)
if err != nil {
log.Fatal(err)
}

os.Stdout.Write(b)

return b
}

func writeResults(output []byte, filename string) {
if err := os.WriteFile(filename, output, os.ModePerm); err != nil {
log.Fatal(err)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package summary

import "strings"

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package summary

import (
"testing"
Expand Down

0 comments on commit ba75c78

Please sign in to comment.