Skip to content
Open
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
7 changes: 5 additions & 2 deletions bazel/container_structure_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,13 @@ fi
"""

def _structure_test_impl(ctx):
# --test-report writes to the file specified by $XML_OUTPUT_FILE
# --test-report-format junit ensures bazel-compatible JUnit XML output
# stdout uses default text format for human-readable output (captured as test.log)
fixed_args = [
"--test-report $XML_OUTPUT_FILE",
"--output junit",
"--junit-suite-name $TEST_TARGET"
"--test-report-format junit",
"--junit-suite-name $TEST_TARGET",
]
test_bin = ctx.toolchains["@container_structure_test//bazel:structure_test_toolchain_type"].st_info.binary
jq_bin = ctx.toolchains["@aspect_bazel_lib//lib:jq_toolchain_type"].jqinfo.bin
Expand Down
38 changes: 25 additions & 13 deletions cmd/container-structure-test/app/cmd/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,30 @@ func NewCmdTest(out io.Writer) *cobra.Command {
return test.ValidateArgs(opts)
},
RunE: func(cmd *cobra.Command, _ []string) error {
if opts.TestReport != "" {
// Force JsonOutput
if opts.Output == unversioned.Text {
opts.JSON = true
opts.Output = unversioned.Json
// Open test report file if specified
var reportOut io.Writer
reportFormatFlag := cmd.Flags().Lookup("test-report-format")

logrus.Warn("raw text format unsupported for writing output file, defaulting to JSON")
if opts.TestReport != "" {
// Validate report format - only json and junit are supported
if opts.TestReportFormat == unversioned.Text {
if reportFormatFlag.Changed {
// User explicitly set --test-report-format text, which is not supported
return fmt.Errorf("--test-report-format does not support 'text'; use 'json' or 'junit'")
}
// Default to JSON for backward compatibility (Text is zero value when flag not set)
opts.TestReportFormat = unversioned.Json
}

testReportFile, err := os.Create(opts.TestReport)
if err != nil {
return err
}
rootCmd.SetOutput(testReportFile)
out = testReportFile // override writer
defer testReportFile.Close()
reportOut = testReportFile
} else if reportFormatFlag.Changed {
// User specified --test-report-format without --test-report
return fmt.Errorf("--test-report-format requires --test-report to be specified")
}

if opts.Quiet {
Expand All @@ -86,15 +96,15 @@ func NewCmdTest(out io.Writer) *cobra.Command {
opts.Output = unversioned.Json
}

return run(out)
return run(out, reportOut)
},
}

AddTestFlags(testCmd)
return testCmd
}

func run(out io.Writer) error {
func run(out, reportOut io.Writer) error {
args = &drivers.DriverConfig{
Image: opts.ImagePath,
Save: opts.Save,
Expand Down Expand Up @@ -153,7 +163,7 @@ func run(out io.Writer) error {
}
var r string
if r, err = daemon.Write(tag, img); err != nil {
logrus.Fatalf("error loading oci layout into daemon: %v, %s", err)
logrus.Fatalf("error loading oci layout into daemon: %v", err)
}
// For some reason, daemon.Write doesn't return errors for some edge cases.
// We should always print what the daemon sent back so that errors are transparent.
Expand Down Expand Up @@ -205,7 +215,7 @@ func run(out io.Writer) error {
channel := make(chan interface{}, 1)
go runTests(out, channel, args, driverImpl)
// TODO(nkubala): put a sync.WaitGroup here
return test.ProcessResults(out, opts.Output, opts.JunitSuiteName, channel)
return test.ProcessResults(out, reportOut, opts.Output, opts.TestReportFormat, opts.JunitSuiteName, channel)
}

func runTests(out io.Writer, channel chan interface{}, args *drivers.DriverConfig, driverImpl func(drivers.DriverConfig) (drivers.Driver, error)) {
Expand Down Expand Up @@ -250,5 +260,7 @@ func AddTestFlags(cmd *cobra.Command) {

cmd.Flags().StringArrayVarP(&opts.ConfigFiles, "config", "c", []string{}, "test config files")
cmd.MarkFlagRequired("config")
cmd.Flags().StringVar(&opts.TestReport, "test-report", "", "generate test report and write it to specified file (supported format: json, junit; default: json)")
cmd.Flags().StringVar(&opts.TestReport, "test-report", "", "generate test report and write it to specified file")
cmd.Flags().VarP(&opts.TestReportFormat, "test-report-format", "", "format for the test report file (json, junit)")
cmd.Flags().Lookup("test-report-format").DefValue = "json"
}
28 changes: 22 additions & 6 deletions cmd/container-structure-test/app/cmd/test/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,14 @@ func Parse(fp string, args *drivers.DriverConfig, driverImpl func(drivers.Driver
return tests, nil
}

func ProcessResults(out io.Writer, format unversioned.OutputValue, junitSuiteName string, c chan interface{}) error {
// ProcessResults processes test results and writes output to the appropriate destinations.
// - out: primary output writer (stdout) - format controlled by the format parameter
// - reportOut: optional test report writer - format controlled by reportFormat parameter
// - format: output format for the primary output (text, json, or junit)
// - reportFormat: output format for the report file (json or junit)
// - junitSuiteName: name for the JUnit test suite
// - c: channel of test results
func ProcessResults(out, reportOut io.Writer, format, reportFormat unversioned.OutputValue, junitSuiteName string, c chan interface{}) error {
totalPass := 0
totalFail := 0
totalDuration := time.Duration(0)
Expand All @@ -116,7 +123,7 @@ func ProcessResults(out io.Writer, format unversioned.OutputValue, junitSuiteNam
}
for _, r := range results {
if format == unversioned.Text {
// output individual results if we're not in json mode
// output individual results if we're in text mode
output.OutputResult(out, r)
}
if r.IsPass() {
Expand All @@ -139,11 +146,20 @@ func ProcessResults(out io.Writer, format unversioned.OutputValue, junitSuiteNam
Fail: totalFail,
Duration: totalDuration,
}
if format == unversioned.Json || format == unversioned.Junit {
// only output results here if we're in json mode
summary.Results = results

summary.Results = results

// Write final summary to primary output (stdout)
if outputErr := output.FinalResults(out, format, junitSuiteName, summary); outputErr != nil {
return outputErr
}

// If a test report file is specified, write to it using the report format
if reportOut != nil {
if reportErr := output.FinalResults(reportOut, reportFormat, junitSuiteName, summary); reportErr != nil {
return reportErr
}
}
output.FinalResults(out, format, junitSuiteName, summary)

return err
}
Expand Down
1 change: 1 addition & 0 deletions pkg/config/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type StructureTestOptions struct {
Platform string
Metadata string
TestReport string
TestReportFormat unversioned.OutputValue
ConfigFiles []string

JSON bool
Expand Down