Skip to content

Commit

Permalink
feat: support to run test case in batch mode (#548)
Browse files Browse the repository at this point in the history
* feat: support to run test case in batch mode

* support to the interval on batch mode

* support to use a template as interval

---------

Co-authored-by: rick <linuxsuren@users.noreply.github.com>
  • Loading branch information
LinuxSuRen and LinuxSuRen authored Oct 15, 2024
1 parent dcdac9e commit 72c53c2
Show file tree
Hide file tree
Showing 15 changed files with 1,512 additions and 932 deletions.
13 changes: 10 additions & 3 deletions console/atest-ui/src/views/StoreManager.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { reactive, ref, watch } from 'vue'
import { Edit, Delete, QuestionFilled } from '@element-plus/icons-vue'
import { Edit, Delete, QuestionFilled, Help } from '@element-plus/icons-vue'
import type { FormInstance, FormRules } from 'element-plus'
import type { Pair } from './types'
import { API } from './net'
Expand Down Expand Up @@ -111,8 +111,7 @@ Magic.Keys(addStore, ['Alt+KeyN'])
const rules = reactive<FormRules<Store>>({
name: [{ required: true, message: 'Name is required', trigger: 'blur' }],
url: [{ required: true, message: 'URL is required', trigger: 'blur' }],
pluginName: [{ required: true, message: 'Plugin is required', trigger: 'blur' }]
url: [{ required: true, message: 'URL is required', trigger: 'blur' }]
})
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
Expand All @@ -130,6 +129,7 @@ const submitForm = async (formEl: FormInstance | undefined) => {
watch(() => storeForm.kind.name, (name) => {
const ext = SupportedExtension(name)
if (ext) {
storeExtLink.value = ext.link
let pro = storeForm.properties.slice()
for (var i = 0; i < pro.length;) {
Expand Down Expand Up @@ -198,6 +198,8 @@ function updateKeys() {
} as Pair)
}
}
const storeExtLink = ref('')
</script>

<template>
Expand Down Expand Up @@ -294,6 +296,11 @@ function updateKeys() {
:value="item.name"
/>
</el-select>
<el-icon v-if="storeExtLink && storeExtLink !== ''">
<el-link :href="storeExtLink" target="_blank">
<Help />
</el-link>
</el-icon>
</el-form-item>
<el-form-item :label="t('field.pluginURL')" prop="plugin">
<el-input v-model="storeForm.kind.url" test-id="store-form-plugin" />
Expand Down
69 changes: 57 additions & 12 deletions console/atest-ui/src/views/TestCase.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { Pair, TestResult, TestCaseWithSuite, TestCase } from './types'
import { NewSuggestedAPIsQuery, CreateFilter, GetHTTPMethods, FlattenObject } from './types'
import { Cache } from './cache'
import { API } from './net'
import type { RunTestCaseRequest } from './net'
import { UIAPI } from './net-vue'
import type { TestCaseResponse } from './cache'
import { Magic } from './magicKeys'
Expand Down Expand Up @@ -59,27 +60,44 @@ const sendRequest = async () => {
}
Magic.Keys(sendRequest, ['Alt+S', 'Alt+ß'])
const runTestCaseResultHandler = (e: any) => {
requestLoading.value = false
handleTestResult(e)
}
const runTestCase = () => {
requestLoading.value = true
const name = props.name
const suite = props.suite
API.RunTestCase({
const request = {
suiteName: suite,
name: name,
parameters: parameters.value
}, (e) => {
handleTestResult(e)
requestLoading.value = false
}, (e) => {
parameters.value = []
} as RunTestCaseRequest
if (batchRunMode.value) {
API.BatchRunTestCase({
count: batchRunCount.value,
interval: batchRunInterval.value,
request: request
}, runTestCaseResultHandler, (e) => {
parameters.value = []
requestLoading.value = false
UIAPI.ErrorTip(e)
parseResponseBody(e.body)
})
} else {
API.RunTestCase(request, runTestCaseResultHandler, (e) => {
parameters.value = []
requestLoading.value = false
UIAPI.ErrorTip(e)
parseResponseBody(e.body)
})
requestLoading.value = false
UIAPI.ErrorTip(e)
parseResponseBody(e.body)
})
}
}
const parseResponseBody = (body) => {
const parseResponseBody = (body: any) => {
if (body === '') {
return
}
Expand All @@ -93,7 +111,7 @@ const parseResponseBody = (body) => {
}
}
const handleTestResult = (e) => {
const handleTestResult = (e: any) => {
testResult.value = e;
if (!isHistoryTestCase.value) {
Expand Down Expand Up @@ -149,6 +167,13 @@ function responseBodyFilter() {
}
const parameterDialogOpened = ref(false)
const batchRunMode = ref(false)
const batchRunCount = ref(1)
const batchRunInterval = ref('1s')
const openBatchRunDialog = () => {
batchRunMode.value = true
openParameterDialog()
}
function openParameterDialog() {
API.GetTestSuite(props.suite, (e) => {
parameters.value = e.param
Expand All @@ -159,6 +184,7 @@ function openParameterDialog() {
function sendRequestWithParameter() {
parameterDialogOpened.value = false
sendRequest()
batchRunMode.value = false
}
function generateCode() {
Expand Down Expand Up @@ -915,6 +941,7 @@ Magic.Keys(() => {
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="openParameterDialog">{{ t('button.sendWithParam') }}</el-dropdown-item>
<el-dropdown-item @click="openBatchRunDialog">Batch Send</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
Expand Down Expand Up @@ -1238,6 +1265,24 @@ Magic.Keys(() => {
<h4>{{ t('title.apiRequestParameter') }}</h4>
</template>
<template #default>
<div v-if="batchRunMode">
<el-row>
<el-col :span="6">
Count:
</el-col>
<el-col :span="18">
<el-input v-model="batchRunCount" type="number" min="1" max="100"/>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
Interval:
</el-col>
<el-col :span="18">
<el-input v-model="batchRunInterval" />
</el-col>
</el-row>
</div>
<el-table :data="parameters" style="width: 100%"
:empty-text="t('tip.noParameter')">
<el-table-column :label="t('field.key')" width="180">
Expand Down
47 changes: 46 additions & 1 deletion console/atest-ui/src/views/net.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,12 @@ interface RunTestCaseRequest {
parameters: any
}

interface BatchRunTestCaseRequest {
count: number
interval: string
request: RunTestCaseRequest
}

function RunTestCase(request: RunTestCaseRequest,
callback: (d: any) => void, errHandle?: (e: any) => void | null) {
const requestOptions = {
Expand All @@ -301,6 +307,45 @@ function RunTestCase(request: RunTestCaseRequest,
.then(callback).catch(emptyOrDefault(errHandle))
}

const BatchRunTestCase = (request: BatchRunTestCaseRequest,
callback: (d: any) => void, errHandle?: (e: any) => void | null) => {
const requestOptions = {
method: 'POST',
headers: {
'X-Store-Name': Cache.GetCurrentStore().name,
'Accept': 'text/event-stream',
'X-Auth': getToken()
},
body: JSON.stringify({
suiteName: request.request.suiteName,
caseName: request.request.name,
parameters: request.request.parameters,
count: request.count,
interval: request.interval,
})
}
fetch(`/api/v1/batchRun`, requestOptions)
.then((response: any) => {
if (response.ok) {
const read = (reader: any) => {
reader.read().then((data: any) => {
if (data.done) {
return;
}

const value = data.value;
const chunk = new TextDecoder().decode(value, { stream: true });
callback(JSON.parse(chunk).result.testCaseResult[0]);
read(reader);
});
}
read(response.body.getReader());
} else {
return DefaultResponseProcess(response)
}
}).catch(emptyOrDefault(errHandle))
}

function DuplicateTestCase(sourceSuiteName: string, targetSuiteName: string,
sourceTestCaseName: string, targetTestCaseName: string,
callback: (d: any) => void, errHandle?: ((reason: any) => PromiseLike<never>) | undefined | null ) {
Expand Down Expand Up @@ -690,7 +735,7 @@ export const API = {
DefaultResponseProcess,
GetVersion,
CreateTestSuite, UpdateTestSuite, ImportTestSuite, GetTestSuite, DeleteTestSuite, ConvertTestSuite, DuplicateTestSuite, GetTestSuiteYaml,
CreateTestCase, UpdateTestCase, GetTestCase, ListTestCase, DeleteTestCase, RunTestCase,
CreateTestCase, UpdateTestCase, GetTestCase, ListTestCase, DeleteTestCase, RunTestCase, BatchRunTestCase,
GetHistoryTestCaseWithResult, DeleteHistoryTestCase,GetHistoryTestCase, GetTestCaseAllHistory, DeleteAllHistoryTestCase, DownloadResponseFile,
GenerateCode, ListCodeGenerator, HistoryGenerateCode,
PopularHeaders,
Expand Down
18 changes: 12 additions & 6 deletions console/atest-ui/src/views/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type { Pair } from './types'

interface Store {
name: string;
link: string;
params: Pair[];
}

Expand All @@ -37,7 +38,8 @@ const storeExtensions = [
}, {
key: 'name',
description: 'See also: git config --local user.name xxx'
}]
}],
link: 'https://github.com/LinuxSuRen/atest-ext-store-git'
},
{
name: 'atest-store-s3',
Expand All @@ -55,26 +57,29 @@ const storeExtensions = [
key: 'forcepathstyle'
}, {
key: 'bucket'
}]
}],
link: 'https://github.com/LinuxSuRen/atest-ext-store-s3'
},
{
name: 'atest-store-orm',
params: [{
key: 'driver',
defaultValue: 'mysql',
description: 'Supported: mysql, postgres'
description: 'Supported: mysql, postgres, sqlite'
}, {
key: 'database',
defaultValue: 'atest'
}, {
key: 'historyLimit',
defaultValue: '',
description: 'Set the limit of the history record count'
}]
}],
link: 'https://github.com/LinuxSuRen/atest-ext-store-orm'
},
{
name: 'atest-store-etcd',
params: []
params: [],
link: 'https://github.com/LinuxSuRen/atest-ext-store-etcd'
},
{
name: 'atest-store-mongodb',
Expand All @@ -83,7 +88,8 @@ const storeExtensions = [
}, {
key: 'database',
defaultValue: 'atest'
}]
}],
link: 'https://github.com/LinuxSuRen/atest-ext-store-mongodb'
}
] as Store[]

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,5 @@ require (
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v2 v2.4.0 // indirect
)
2 changes: 1 addition & 1 deletion pkg/runner/monitor/monitor.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pkg/runner/monitor/monitor_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 49 additions & 0 deletions pkg/server/remote_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
reflect "reflect"
"regexp"
"strings"
"time"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
Expand Down Expand Up @@ -294,6 +295,54 @@ func (s *server) Run(ctx context.Context, task *TestTask) (reply *TestResult, er
return
}

func (s *server) BatchRun(srv Runner_BatchRunServer) (err error) {
ctx := srv.Context()
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
var in *BatchTestTask
in, err = srv.Recv()
if err != nil {
if err == io.EOF {
return nil
}
return err
}

for i := 0; i < int(in.Count); i++ {
var reply *TestCaseResult
if reply, err = s.RunTestCase(ctx, &TestCaseIdentity{
Suite: in.SuiteName,
Testcase: in.CaseName,
}); err != nil {
return
}

if err = srv.Send(&TestResult{
TestCaseResult: []*TestCaseResult{reply},
Error: reply.Error,
}); err != nil {
return err
}

var interval string
if interval, err = render.Render("batch run interval", in.Interval, nil); err != nil {
return
}

var duration time.Duration
if duration, err = time.ParseDuration(interval); err != nil {
return
}
time.Sleep(duration)
}
}
}
return
}

func handleLargeResponseBody(resp runner.SimpleResponse, suite string, caseName string) (reply runner.SimpleResponse, err error) {
const maxSize = 5120
prefix := "isFilePath-" + strings.Join([]string{suite, caseName}, "-")
Expand Down
Loading

0 comments on commit 72c53c2

Please sign in to comment.