Skip to content

Commit fd2a747

Browse files
authored
add docker compose extension support (#679)
Co-authored-by: rick <LinuxSuRen@users.noreply.github.com>
1 parent e088f57 commit fd2a747

18 files changed

+220
-28
lines changed

cmd/compose.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
Copyright 2025 API Testing Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cmd
18+
19+
import (
20+
"fmt"
21+
"strings"
22+
23+
"github.com/spf13/cobra"
24+
)
25+
26+
type composeOptions struct {
27+
runOption
28+
projectName string
29+
}
30+
31+
func createComposeRun() *cobra.Command {
32+
cmd := &cobra.Command{
33+
Use: "compose",
34+
}
35+
36+
cmd.AddCommand(createComposeRunUp())
37+
return cmd
38+
}
39+
40+
type composeMsgWriter struct {
41+
}
42+
43+
func (w *composeMsgWriter) Write(p []byte) (n int, err error) {
44+
fmt.Println(`{ "type": "info", "message": "` + strings.TrimSpace(string(p)) + `" }`)
45+
return
46+
}
47+
48+
func (o *composeOptions) preRunE(cmd *cobra.Command, args []string) error {
49+
return o.runOption.preRunE(cmd, nil)
50+
}
51+
52+
func (o *composeOptions) runE(cmd *cobra.Command, args []string) error {
53+
return o.runOption.runE(cmd, args)
54+
}
55+
56+
func createComposeRunUp() *cobra.Command {
57+
opt := &composeOptions{
58+
runOption: *newDefaultRunOption(&composeMsgWriter{}),
59+
}
60+
c := &cobra.Command{
61+
Use: "up",
62+
PreRunE: opt.preRunE,
63+
RunE: opt.runE,
64+
}
65+
c.SetOut(&composeMsgWriter{})
66+
67+
c.Flags().StringVarP(&opt.projectName, "project-name", "", "", "Specify an alternate project name")
68+
opt.runOption.addFlags(c.Flags())
69+
return c
70+
}

cmd/root.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ func NewRootCmd(execer fakeruntime.Execer, httpServer server.HTTPServer) (c *cob
3939
createRunCommand(), createSampleCmd(), createMockComposeCmd(),
4040
createServerCmd(execer, httpServer), createJSONSchemaCmd(),
4141
createServiceCommand(execer), createFunctionCmd(), createConvertCommand(),
42-
createMockCmd(), createExtensionCommand(downloader.NewStoreDownloader()))
42+
createMockCmd(), createExtensionCommand(downloader.NewStoreDownloader()),
43+
createComposeRun())
4344
return
4445
}
4546

cmd/run.go

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,13 @@ var (
7979
runLogger = logging.DefaultLogger(logging.LogLevelInfo).WithName("run")
8080
)
8181

82-
func newDefaultRunOption() *runOption {
82+
func newDefaultRunOption(output io.Writer) *runOption {
83+
if output == nil {
84+
output = os.Stdout
85+
}
8386
return &runOption{
8487
reporter: runner.NewMemoryTestReporter(nil, ""),
85-
reportWriter: runner.NewResultWriter(os.Stdout),
88+
reportWriter: runner.NewResultWriter(output),
8689
loader: testing.NewFileLoader(),
8790
githubReportOption: &runner.GithubPRCommentOption{},
8891
}
@@ -97,7 +100,7 @@ func newDiscardRunOption() *runOption {
97100

98101
// createRunCommand returns the run command
99102
func createRunCommand() (cmd *cobra.Command) {
100-
opt := newDefaultRunOption()
103+
opt := newDefaultRunOption(nil)
101104
cmd = &cobra.Command{
102105
Use: "run",
103106
Aliases: []string{"r"},
@@ -111,29 +114,33 @@ See also https://github.com/LinuxSuRen/api-testing/tree/master/sample`,
111114

112115
// set flags
113116
flags := cmd.Flags()
114-
flags.StringVarP(&opt.pattern, "pattern", "p", "test-suite-*.yaml",
115-
"The file pattern which try to execute the test cases. Brace expansion is supported, such as: test-suite-{1,2}.yaml")
116-
flags.StringVarP(&opt.level, "level", "l", "info", "Set the output log level")
117-
flags.DurationVarP(&opt.duration, "duration", "", 0, "Running duration")
118-
flags.DurationVarP(&opt.requestTimeout, "request-timeout", "", time.Minute, "Timeout for per request")
119-
flags.BoolVarP(&opt.requestIgnoreError, "request-ignore-error", "", false, "Indicate if ignore the request error")
120-
flags.StringArrayVarP(&opt.caseFilter, "case-filter", "", nil, "The filter of the test case")
121-
flags.StringVarP(&opt.report, "report", "", "", "The type of target report. Supported: markdown, md, html, json, discard, std, prometheus, http, grpc")
122-
flags.StringVarP(&opt.reportFile, "report-file", "", "", "The file path of the report")
123-
flags.BoolVarP(&opt.reportIgnore, "report-ignore", "", false, "Indicate if ignore the report output")
124-
flags.StringVarP(&opt.reportTemplate, "report-template", "", "", "The template used to render the report")
125-
flags.StringVarP(&opt.reportDest, "report-dest", "", "", "The server url where you want to send the report")
126-
flags.StringVarP(&opt.swaggerURL, "swagger-url", "", "", "The URL of swagger")
127-
flags.Int64VarP(&opt.thread, "thread", "", 1, "Threads of the execution")
128-
flags.Int32VarP(&opt.qps, "qps", "", 5, "QPS")
129-
flags.Int32VarP(&opt.burst, "burst", "", 5, "burst")
130-
flags.StringVarP(&opt.monitorDocker, "monitor-docker", "", "", "The docker container name to monitor")
117+
opt.addFlags(flags)
131118
addGitHubReportFlags(flags, opt.githubReportOption)
132119
return
133120
}
134121

135122
const caseFilter = "case-filter"
136123

124+
func (o *runOption) addFlags(flags *pflag.FlagSet) {
125+
flags.StringVarP(&o.pattern, "pattern", "p", "test-suite-*.yaml",
126+
"The file pattern which try to execute the test cases. Brace expansion is supported, such as: test-suite-{1,2}.yaml")
127+
flags.StringVarP(&o.level, "level", "l", "info", "Set the output log level")
128+
flags.DurationVarP(&o.duration, "duration", "", 0, "Running duration")
129+
flags.DurationVarP(&o.requestTimeout, "request-timeout", "", time.Minute, "Timeout for per request")
130+
flags.BoolVarP(&o.requestIgnoreError, "request-ignore-error", "", false, "Indicate if ignore the request error")
131+
flags.StringArrayVarP(&o.caseFilter, "case-filter", "", nil, "The filter of the test case")
132+
flags.StringVarP(&o.report, "report", "", "", "The type of target report. Supported: markdown, md, html, json, discard, std, prometheus, http, grpc")
133+
flags.StringVarP(&o.reportFile, "report-file", "", "", "The file path of the report")
134+
flags.BoolVarP(&o.reportIgnore, "report-ignore", "", false, "Indicate if ignore the report output")
135+
flags.StringVarP(&o.reportTemplate, "report-template", "", "", "The template used to render the report")
136+
flags.StringVarP(&o.reportDest, "report-dest", "", "", "The server url where you want to send the report")
137+
flags.StringVarP(&o.swaggerURL, "swagger-url", "", "", "The URL of swagger")
138+
flags.Int64VarP(&o.thread, "thread", "", 1, "Threads of the execution")
139+
flags.Int32VarP(&o.qps, "qps", "", 5, "QPS")
140+
flags.Int32VarP(&o.burst, "burst", "", 5, "burst")
141+
flags.StringVarP(&o.monitorDocker, "monitor-docker", "", "", "The docker container name to monitor")
142+
}
143+
137144
func (o *runOption) preRunE(cmd *cobra.Command, args []string) (err error) {
138145
ctx := cmd.Context()
139146
if ctx == nil {
@@ -148,6 +155,9 @@ func (o *runOption) preRunE(cmd *cobra.Command, args []string) (err error) {
148155
return
149156
}
150157

158+
defer func() {
159+
_ = reportFile.Close()
160+
}()
151161
writer = io.MultiWriter(writer, reportFile)
152162
}
153163

@@ -354,7 +364,7 @@ func (o *runOption) runSuite(loader testing.Loader, dataContext map[string]inter
354364
suiteRunner := runner.GetTestSuiteRunner(testSuite)
355365
suiteRunner.WithTestReporter(o.reporter)
356366
suiteRunner.WithSecure(testSuite.Spec.Secure)
357-
suiteRunner.WithOutputWriter(os.Stdout)
367+
suiteRunner.WithOutputWriter(o.reportWriter.GetWriter())
358368
suiteRunner.WithWriteLevel(o.level)
359369
suiteRunner.WithSuite(testSuite)
360370
var caseFilterObj interface{}

docs/site/content/zh/latest/tasks/code-generator.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
+++
22
title = "代码生成"
3-
weight = 103
3+
weight = 104
44
+++
55

66
`atest` 支持把测试用例生成多种开发语言的代码:
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
+++
2+
title = "End-to-End"
3+
weight = 103
4+
+++
5+
6+
`atest` 非常适合针对(HTTP)接口做 E2E(端到端)测试,E2E 测试可以确保后端接口在完整的环境中持续地保持正确运行。下面采用 Docker compose 给出一个使用事例:
7+
8+
```yaml
9+
version: '3.1'
10+
services:
11+
testing:
12+
image: ghcr.io/linuxsuren/api-testing:latest
13+
environment:
14+
SERVER: http://server:8080
15+
volumes:
16+
- ./testsuite.yaml:/work/testsuite.yaml
17+
command: atest run -p /work/testsuite.yaml
18+
depends_on:
19+
server:
20+
condition: service_healthy
21+
links:
22+
- server
23+
server:
24+
image: ghcr.io/devops-ws/learn-springboot:master
25+
healthcheck:
26+
test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/8080"]
27+
interval: 3s
28+
timeout: 60s
29+
retries: 10
30+
start_period: 3s
31+
```
32+
33+
从 Docker compose `v2.36.0` 开始,可以采用如下简化的写法:
34+
35+
```yaml
36+
services:
37+
testing:
38+
scale: 0
39+
provider:
40+
type: atest
41+
options:
42+
pattern: testsuite.yaml
43+
environment:
44+
SERVER: http://server:8080
45+
depends_on:
46+
server:
47+
condition: service_healthy
48+
links:
49+
- server
50+
server:
51+
image: ghcr.io/devops-ws/learn-springboot:master
52+
healthcheck:
53+
test: ["CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/8080"]
54+
interval: 3s
55+
timeout: 60s
56+
retries: 10
57+
start_period: 3s
58+
```
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!api-testing
2+
# yaml-language-server: $schema=https://linuxsuren.github.io/api-testing/api-testing-schema.json
3+
name: SpringBoot
4+
api: |
5+
{{default "http://localhost:8080" (env "SERVER")}}
6+
items:
7+
- name: health
8+
request:
9+
api: /health

main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ package main
22

33
import (
44
_ "embed"
5-
"github.com/linuxsuren/api-testing/pkg/version"
65
"os"
76

7+
"github.com/linuxsuren/api-testing/pkg/version"
8+
89
"github.com/linuxsuren/api-testing/cmd"
910
"github.com/linuxsuren/api-testing/pkg/server"
1011
exec "github.com/linuxsuren/go-fake-runtime"

pkg/logging/log.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,12 @@ func DefaultLogger(level LogLevel) Logger {
118118
// contain only letters, digits, and hyphens (see the package documentation for
119119
// more information).
120120
func (l Logger) WithName(name string) Logger {
121+
return l.WithNameAndWriter(name, os.Stdout)
122+
}
123+
124+
func (l Logger) WithNameAndWriter(name string, writer io.Writer) Logger {
121125
logLevel := l.logging.Level[APITestingLogComponent(name)]
122-
logger := initZapLogger(os.Stdout, l.logging, logLevel)
126+
logger := initZapLogger(writer, l.logging, logLevel)
123127

124128
return Logger{
125129
Logger: zapr.NewLogger(logger).WithName(name),

pkg/runner/writer.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,16 @@ limitations under the License.
1616

1717
package runner
1818

19-
import "github.com/linuxsuren/api-testing/pkg/apispec"
19+
import (
20+
"io"
21+
22+
"github.com/linuxsuren/api-testing/pkg/apispec"
23+
)
2024

2125
// ReportResultWriter is the interface of the report writer
2226
type ReportResultWriter interface {
2327
Output([]ReportResult) error
2428
WithAPICoverage(apiCoverage apispec.APICoverage) ReportResultWriter
2529
WithResourceUsage([]ResourceUsage) ReportResultWriter
30+
GetWriter() io.Writer
2631
}

pkg/runner/writer_github_pr_comment.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2023 API Testing Authors.
2+
Copyright 2023-2025 API Testing Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -194,6 +194,10 @@ func (w *githubPRCommentWriter) WithResourceUsage([]ResourceUsage) ReportResultW
194194
return w
195195
}
196196

197+
func (w *githubPRCommentWriter) GetWriter() io.Writer {
198+
return os.Stdout
199+
}
200+
197201
func unmarshalResponseBody(resp *http.Response, expectedCode int, obj interface{}) (err error) {
198202
if resp.StatusCode != expectedCode {
199203
err = fmt.Errorf("expect status code: %d, but %d", expectedCode, resp.StatusCode)

0 commit comments

Comments
 (0)