fix(接口自动化): 循环控制器死循环问题修复

This commit is contained in:
fit2-zhao 2021-01-19 15:54:13 +08:00
parent e2356c1849
commit 387c685455
10 changed files with 220 additions and 42 deletions

View File

@ -11,12 +11,11 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.control.ForeachController;
import org.apache.jmeter.control.GenericController;
import org.apache.jmeter.control.LoopController;
import org.apache.jmeter.control.WhileController;
import org.apache.jmeter.control.*;
import org.apache.jmeter.reporters.ResultAction;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.timers.ConstantTimer;
import org.apache.jorphan.collections.HashTree;
import java.util.List;
@ -43,11 +42,21 @@ public class MsLoopController extends MsTestElement {
if (!this.isEnable()) {
return;
}
GenericController controller = controller();
if (controller == null) {
return;
final HashTree groupTree = controller(tree);
// 不打开执行成功后轮询功能则成功后就停止循环
if (StringUtils.equals(this.loopType, "LOOP_COUNT") && this.countController != null && !countController.isProceed()) {
ResultAction resultAction = new ResultAction();
resultAction.setName("ResultAction");
resultAction.setProperty("OnError.action", "1000");
groupTree.add(resultAction);
}
final HashTree groupTree = tree.add(controller);
// 循环间隔时长设置
ConstantTimer cnstantTimer = getCnstantTimer();
if (cnstantTimer != null) {
groupTree.add(cnstantTimer);
}
if (CollectionUtils.isNotEmpty(hashTree)) {
hashTree.forEach(el -> {
el.toHashTree(groupTree, el.getHashTree(), config);
@ -58,14 +67,11 @@ public class MsLoopController extends MsTestElement {
private LoopController loopController() {
LoopController loopController = new LoopController();
loopController.setEnabled(true);
loopController.setName(this.getLabel());
loopController.setName("LoopController");
loopController.setProperty(TestElement.TEST_CLASS, LoopController.class.getName());
loopController.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("LoopControlPanel"));
loopController.setLoops(countController.getLoops());
// 不打开执行成功后轮询功能则成功后就停止循环
if (!countController.isProceed()) {
}
loopController.setContinueForever(false);
return loopController;
}
@ -93,19 +99,23 @@ public class MsLoopController extends MsTestElement {
}
private WhileController whileController() {
String condition = getCondition();
if (StringUtils.isEmpty(condition)) {
return null;
}
WhileController controller = new WhileController();
controller.setEnabled(true);
controller.setName(this.getLabel());
controller.setName("WhileController");
controller.setProperty(TestElement.TEST_CLASS, WhileController.class.getName());
controller.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("WhileControllerGui"));
controller.setCondition(getCondition());
controller.setCondition(condition);
return controller;
}
private ForeachController foreachController() {
ForeachController controller = new ForeachController();
controller.setEnabled(true);
controller.setName(this.getLabel());
controller.setName("ForeachController");
controller.setProperty(TestElement.TEST_CLASS, ForeachController.class.getName());
controller.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ForeachControlPanel"));
controller.setInputVal(this.forEachController.getInputVal());
@ -114,15 +124,49 @@ public class MsLoopController extends MsTestElement {
return controller;
}
private GenericController controller() {
private HashTree controller(HashTree tree) {
if (StringUtils.equals(this.loopType, "WHILE") && this.whileController != null) {
return whileController();
RunTime runTime = new RunTime();
runTime.setEnabled(true);
runTime.setProperty(TestElement.TEST_CLASS, RunTime.class.getName());
runTime.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("RunTimeGui"));
long timeout = this.whileController.getTimeout() / 1000;
if (timeout < 1) {
timeout = 1;
}
runTime.setRuntime(timeout);
// 添加超时处理防止死循环
HashTree hashTree = tree.add(runTime);
return hashTree.add(whileController());
}
if (StringUtils.equals(this.loopType, "FOREACH") && this.forEachController != null) {
return foreachController();
return tree.add(foreachController());
}
if (StringUtils.equals(this.loopType, "LOOP_COUNT") && this.countController != null) {
return loopController();
return tree.add(loopController());
}
return null;
}
private ConstantTimer getCnstantTimer() {
ConstantTimer constantTimer = new ConstantTimer();
constantTimer.setEnabled(true);
constantTimer.setProperty(TestElement.TEST_CLASS, ConstantTimer.class.getName());
constantTimer.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ConstantTimerGui"));
if (StringUtils.equals(this.loopType, "WHILE") && this.whileController != null) {
return null;
}
if (StringUtils.equals(this.loopType, "FOREACH") && this.forEachController != null) {
constantTimer.setProperty("ConstantTimer.delay", this.forEachController.getInterval());
constantTimer.setDelay(this.forEachController.getInterval());
constantTimer.setName(this.forEachController.getInterval() + " ms");
return constantTimer;
}
if (StringUtils.equals(this.loopType, "LOOP_COUNT") && this.countController != null) {
constantTimer.setProperty("ConstantTimer.delay", this.countController.getInterval() + "");
constantTimer.setDelay(this.countController.getInterval() + "");
constantTimer.setName(this.countController.getInterval() + " ms");
return constantTimer;
}
return null;
}

View File

@ -39,6 +39,9 @@ public class MsJSR223Processor extends MsTestElement {
} else {
processor.setName("JSR223Processor");
}
if (config != null && StringUtils.isNotEmpty(config.getStep())) {
processor.setName(this.getName() + "<->" + config.getStep());
}
processor.setProperty(TestElement.TEST_CLASS, JSR223Sampler.class.getName());
processor.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TestBeanGUI"));
processor.setProperty("cacheKey", "true");

View File

@ -24,6 +24,7 @@ import io.metersphere.commons.utils.ServiceUtils;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.i18n.Translator;
import io.metersphere.track.service.TestPlanReportService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -211,13 +212,19 @@ public class ApiScenarioReportService {
}
TestPlanReportService testPlanReportService = CommonBeanFactory.getBean(TestPlanReportService.class);
testPlanReportService.updateReport(testPlanReportIdList,ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(), ReportTriggerMode.SCHEDULE.name());
testPlanReportService.updateReport(testPlanReportIdList, ApiRunMode.SCHEDULE_SCENARIO_PLAN.name(), ReportTriggerMode.SCHEDULE.name());
return lastReport;
}
public ApiScenarioReport updateScenario(TestResult result) {
ApiScenarioReport lastReport = null;
if (CollectionUtils.isEmpty(result.getScenarios())) {
ScenarioResult test = new ScenarioResult();
test.setName(result.getTestId());
ApiScenarioReport report = editReport(test);
return report;
}
for (ScenarioResult item : result.getScenarios()) {
// 更新报告状态
ApiScenarioReport report = editReport(item);

View File

@ -0,0 +1,98 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.jmeter.reporters;
import org.apache.jmeter.samplers.SampleEvent;
import org.apache.jmeter.samplers.SampleListener;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.OnErrorTestElement;
import org.apache.jmeter.threads.JMeterContext.TestLogicalAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
/**
* ResultAction - take action based on the status of the last Result
*/
public class ResultAction extends OnErrorTestElement implements Serializable, SampleListener {
private static final long serialVersionUID = 242L;
private static final Logger log = LoggerFactory.getLogger(ResultAction.class);
/**
* Constructor is initially called once for each occurrence in the test plan
* For GUI, several more instances are created Then clear is called at start
* of test Called several times during test startup The name will not
* necessarily have been set at this point.
*/
public ResultAction() {
super();
}
/**
* Examine the sample(s) and take appropriate action
*
* @see SampleListener#sampleOccurred(SampleEvent)
*/
@Override
public void sampleOccurred(SampleEvent e) {
SampleResult s = e.getResult();
if (log.isDebugEnabled()) {
log.debug("ResultStatusHandler {} for {} OK? {}", getName(), s.getSampleLabel(), s.isSuccessful());
}
if (!s.isSuccessful()) {
if (isStopTestNow()) {
s.setStopTestNow(true);
} else if (isStopTest()) {
s.setStopTest(true);
} else if (isStopThread()) {
s.setStopThread(true);
} else if (isStartNextThreadLoop()) {
s.setTestLogicalAction(TestLogicalAction.START_NEXT_ITERATION_OF_THREAD);
} else if (isStartNextIterationOfCurrentLoop()) {
s.setTestLogicalAction(TestLogicalAction.START_NEXT_ITERATION_OF_CURRENT_LOOP);
} else if (isBreakCurrentLoop()) {
s.setTestLogicalAction(TestLogicalAction.BREAK_CURRENT_LOOP);
}
} else {
if (getErrorAction() == 1000) {
s.setTestLogicalAction(TestLogicalAction.BREAK_CURRENT_LOOP);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void sampleStarted(SampleEvent e) {
// not used
}
/**
* {@inheritDoc}
*/
@Override
public void sampleStopped(SampleEvent e) {
// not used
}
}

View File

@ -110,6 +110,10 @@
if (this.isNotRunning) {
try {
this.content = JSON.parse(this.report.content);
console.log(this.content)
if (!this.content) {
this.content = {scenarios: []};
}
this.$emit('refresh');
} catch (e) {
throw e;
@ -130,21 +134,23 @@
if (this.isNotRunning) {
this.fails = [];
this.totalTime = 0
this.content.scenarios.forEach((scenario) => {
this.totalTime = this.totalTime + Number(scenario.responseTime)
let failScenario = Object.assign({}, scenario);
if (scenario.error > 0) {
this.fails.push(failScenario);
failScenario.requestResults = [];
scenario.requestResults.forEach((request) => {
if (!request.success) {
let failRequest = Object.assign({}, request);
failScenario.requestResults.push(failRequest);
}
})
if (this.content.scenarios) {
this.content.scenarios.forEach((scenario) => {
this.totalTime = this.totalTime + Number(scenario.responseTime)
let failScenario = Object.assign({}, scenario);
if (scenario.error > 0) {
this.fails.push(failScenario);
failScenario.requestResults = [];
scenario.requestResults.forEach((request) => {
if (!request.success) {
let failRequest = Object.assign({}, request);
failScenario.requestResults.push(failRequest);
}
})
}
})
}
})
}
}
},
requestResult(requestResult) {

View File

@ -68,6 +68,10 @@
request: {},
currentScenario: {},
node: {},
draggable: {
type: Boolean,
default: false,
},
currentEnvironmentId: String,
},
components: {

View File

@ -31,6 +31,10 @@
props: {
scenario: {},
node: {},
draggable: {
type: Boolean,
default: false,
},
},
watch: {},
created() {

View File

@ -10,7 +10,7 @@
:title="$t('api_test.automation.wait_controller')">
<template v-slot:headerLeft>
<el-input-number class="time-input" size="small" v-model="timer.delay" :min="0" :step="1000"/>
<el-input-number class="time-input" size="small" v-model="timer.delay" :min="0" :step="1000"/> ms
</template>
</api-base-component>
@ -24,6 +24,10 @@
props: {
timer: {},
node: {},
draggable: {
type: Boolean,
default: false,
},
},
data() {
return {}

View File

@ -33,6 +33,10 @@
controller: {},
node: {},
index: Object,
draggable: {
type: Boolean,
default: false,
},
},
data() {
return {

View File

@ -25,8 +25,8 @@
</el-col>
<el-col :span="8">
<span class="ms-span ms-radio">{{$t('loop.interval')}}</span>
<el-input-number size="small" v-model="controller.countController.interval" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
<span class="ms-span ms-radio"></span>
<el-input-number size="small" v-model="controller.countController.interval" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0" :step="1000"/>
<span class="ms-span ms-radio">ms</span>
</el-col>
<el-col :span="8">
<span class="ms-span ms-radio">{{$t('loop.proceed')}}</span>
@ -51,8 +51,8 @@
</el-col>
<el-col :span="7">
<span class="ms-span ms-radio">{{$t('loop.interval')}}</span>
<el-input-number size="small" v-model="controller.forEachController.interval" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
<span class="ms-span ms-radio"></span>
<el-input-number size="small" v-model="controller.forEachController.interval" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0" :step="1000"/>
<span class="ms-span ms-radio">ms</span>
</el-col>
</el-row>
</div>
@ -65,8 +65,8 @@
</el-select>
<el-input size="small" v-model="controller.whileController.value" :placeholder="$t('api_test.value')" v-if="!hasEmptyOperator" style="width: 20%;margin-left: 20px"/>
<span class="ms-span ms-radio">{{$t('loop.timeout')}}</span>
<el-input-number size="small" v-model="controller.whileController.timeout" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
<span class="ms-span ms-radio"></span>
<el-input-number size="small" v-model="controller.whileController.timeout" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="1" :step="1000"/>
<span class="ms-span ms-radio">ms</span>
</div>
</api-base-component>
@ -82,6 +82,10 @@
controller: {},
node: {},
index: Object,
draggable: {
type: Boolean,
default: false,
},
},
data() {
return {