fix(接口自动化): 循环控制器死循环问题修复
This commit is contained in:
parent
e2356c1849
commit
387c685455
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -68,6 +68,10 @@
|
|||
request: {},
|
||||
currentScenario: {},
|
||||
node: {},
|
||||
draggable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
currentEnvironmentId: String,
|
||||
},
|
||||
components: {
|
||||
|
|
|
@ -31,6 +31,10 @@
|
|||
props: {
|
||||
scenario: {},
|
||||
node: {},
|
||||
draggable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
watch: {},
|
||||
created() {
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -33,6 +33,10 @@
|
|||
controller: {},
|
||||
node: {},
|
||||
index: Object,
|
||||
draggable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue