From b2e619e355198ee05a7e6b742b743fc496bd0ad1 Mon Sep 17 00:00:00 2001 From: fit2-zhao Date: Wed, 6 Jan 2021 14:52:32 +0800 Subject: [PATCH 1/8] =?UTF-8?q?fix(=E6=8E=A5=E5=8F=A3=E5=AE=9A=E4=B9=89):?= =?UTF-8?q?=20DUBBO=E6=89=A7=E8=A1=8C=E7=BC=BA=E9=99=B7=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metersphere/api/jmeter/APIBackendListenerClient.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 9d33942769..79bb79e42d 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -1,5 +1,6 @@ package io.metersphere.api.jmeter; +import io.metersphere.api.dto.scenario.request.RequestType; import io.metersphere.api.service.*; import io.metersphere.base.domain.ApiScenarioReport; import io.metersphere.base.domain.ApiTestReport; @@ -339,7 +340,11 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl String start = "RPC Protocol: "; String end = "://"; if (StringUtils.contains(body, start)) { - return StringUtils.substringBetween(body, start, end).toUpperCase(); + String protocol = StringUtils.substringBetween(body, start, end); + if (StringUtils.isNotEmpty(protocol)) { + return protocol.toUpperCase(); + } + return RequestType.DUBBO; } else { // Http Method String method = StringUtils.substringBefore(body, " "); From 5952b016466ce91298a6bbde44592e097d439c64 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 6 Jan 2021 09:54:19 +0800 Subject: [PATCH 2/8] =?UTF-8?q?refactor:=20=E6=8C=89=E7=85=A7=E8=BF=AD?= =?UTF-8?q?=E4=BB=A3=E6=AC=A1=E6=95=B0=E5=92=8C=E6=89=A7=E8=A1=8C=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E5=8C=BA=E5=88=86=E6=80=A7=E8=83=BD=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xml/reader/jmx/JmeterDocumentParser.java | 150 ++++++++++++++---- .../components/PerformancePressureConfig.vue | 136 +++++++++++----- .../performance/test/EditPerformanceTest.vue | 3 + .../components/PerformancePressureConfig.vue | 145 ++++++++++++----- frontend/src/i18n/en-US.js | 4 + frontend/src/i18n/zh-CN.js | 4 + frontend/src/i18n/zh-TW.js | 4 + 7 files changed, 343 insertions(+), 103 deletions(-) diff --git a/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java b/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java index fe1f20966f..3a07da3ca2 100644 --- a/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java +++ b/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java @@ -96,15 +96,27 @@ public class JmeterDocumentParser implements DocumentParser { processCheckoutArguments(ele); processCheckoutResponseAssertion(ele); } else if (nodeNameEquals(ele, CONCURRENCY_THREAD_GROUP)) { - processConcurrencyThreadGroup(ele); + processThreadGroupName(ele); processCheckoutTimer(ele); processCheckoutBackendListener(ele); } else if (nodeNameEquals(ele, VARIABLE_THROUGHPUT_TIMER)) { processVariableThroughputTimer(ele); } else if (nodeNameEquals(ele, THREAD_GROUP)) { - processThreadGroup(ele); - // - processConcurrencyThreadGroup(ele); + Object threadType = context.getProperty("threadType"); + if (threadType instanceof List) { + Object o = ((List) threadType).get(0); + ((List) threadType).remove(0); + if ("DURATION".equals(o)) { + processThreadGroup(ele); + } + if ("ITERATION".equals(o)) { + processIterationThreadGroup(ele); + } + } else { + processThreadGroup(ele); + } + + processThreadGroupName(ele); processCheckoutTimer(ele); processCheckoutBackendListener(ele); } else if (nodeNameEquals(ele, BACKEND_LISTENER)) { @@ -772,22 +784,120 @@ public class JmeterDocumentParser implements DocumentParser { */ removeChildren(threadGroup); // elementProp + Object targetLevels = context.getProperty("TargetLevel"); + String threads = "10"; + if (targetLevels instanceof List) { + Object o = ((List) targetLevels).get(0); + ((List) targetLevels).remove(0); + threads = o.toString(); + } + Object rampUps = context.getProperty("RampUp"); + String rampUp = "1"; + if (rampUps instanceof List) { + Object o = ((List) rampUps).get(0); + ((List) rampUps).remove(0); + rampUp = o.toString(); + } + Object steps = context.getProperty("Steps"); + String step = "2"; + if (steps instanceof List) { + Object o = ((List) steps).get(0); + ((List) steps).remove(0); + step = o.toString(); + } + Object holds = context.getProperty("Hold"); + String hold = "2"; + if (holds instanceof List) { + Object o = ((List) holds).get(0); + ((List) holds).remove(0); + hold = o.toString(); + } Element elementProp = document.createElement("elementProp"); elementProp.setAttribute("name", "ThreadGroup.main_controller"); elementProp.setAttribute("elementType", "com.blazemeter.jmeter.control.VirtualUserController"); threadGroup.appendChild(elementProp); - threadGroup.appendChild(createStringProp(document, "ThreadGroup.on_sample_error", "continue")); - threadGroup.appendChild(createStringProp(document, "TargetLevel", "2")); - threadGroup.appendChild(createStringProp(document, "RampUp", "12")); - threadGroup.appendChild(createStringProp(document, "Steps", "2")); - threadGroup.appendChild(createStringProp(document, "Hold", "1")); + threadGroup.appendChild(createStringProp(document, "TargetLevel", threads)); + threadGroup.appendChild(createStringProp(document, "RampUp", rampUp)); + threadGroup.appendChild(createStringProp(document, "Steps", step)); + threadGroup.appendChild(createStringProp(document, "Hold", hold)); threadGroup.appendChild(createStringProp(document, "LogFilename", "")); // bzm - Concurrency Thread Group "Thread Iterations Limit:" 设置为空 // threadGroup.appendChild(createStringProp(document, "Iterations", "1")); threadGroup.appendChild(createStringProp(document, "Unit", "S")); } + private void processIterationThreadGroup(Element threadGroup) { + // 检查 threadgroup 后面的hashtree是否为空 + Node hashTree = threadGroup.getNextSibling(); + while (!(hashTree instanceof Element)) { + hashTree = hashTree.getNextSibling(); + } + if (!hashTree.hasChildNodes()) { + MSException.throwException(Translator.get("jmx_content_valid")); + } + // 重命名 tagName + Document document = threadGroup.getOwnerDocument(); + removeChildren(threadGroup); + + // 选择按照迭代次数处理线程组 + /* + continue + + false + 1 + + 100 + 5 + true + 10 + + true + */ + // elementProp + Object targetLevels = context.getProperty("TargetLevel"); + String threads = "10"; + if (targetLevels instanceof List) { + Object o = ((List) targetLevels).get(0); + ((List) targetLevels).remove(0); + threads = o.toString(); + } + Object iterateNum = context.getProperty("iterateNum"); + String loops = "1"; + if (iterateNum instanceof List) { + Object o = ((List) iterateNum).get(0); + ((List) iterateNum).remove(0); + loops = o.toString(); + } + Object rampUps = context.getProperty("iterateRampUpTime"); + String rampUp = "10"; + if (rampUps instanceof List) { + Object o = ((List) rampUps).get(0); + ((List) rampUps).remove(0); + rampUp = o.toString(); + } + Element elementProp = document.createElement("elementProp"); + elementProp.setAttribute("name", "ThreadGroup.main_controller"); + elementProp.setAttribute("elementType", "LoopController"); + elementProp.setAttribute("guiclass", "LoopControlPanel"); + elementProp.setAttribute("testclass", "LoopController"); + elementProp.setAttribute("testname", "Loop Controller"); + elementProp.setAttribute("enabled", "true"); + elementProp.appendChild(createBoolProp(document, "LoopController.continue_forever", false)); + elementProp.appendChild(createStringProp(document, "LoopController.loops", loops)); + threadGroup.appendChild(elementProp); + + threadGroup.appendChild(createStringProp(document, "ThreadGroup.on_sample_error", "continue")); + threadGroup.appendChild(createStringProp(document, "ThreadGroup.num_threads", threads)); + threadGroup.appendChild(createStringProp(document, "ThreadGroup.ramp_time", rampUp)); + threadGroup.appendChild(createBoolProp(document, "ThreadGroup.scheduler", false)); // 不指定执行时间 + threadGroup.appendChild(createStringProp(document, "Hold", "1")); + threadGroup.appendChild(createStringProp(document, "ThreadGroup.duration", "10")); + threadGroup.appendChild(createStringProp(document, "ThreadGroup.delay", "")); + threadGroup.appendChild(createBoolProp(document, "ThreadGroup.same_user_on_next_iteration", true)); + } + + private void processCheckoutTimer(Element element) { /* @@ -856,25 +966,9 @@ public class JmeterDocumentParser implements DocumentParser { return unit; } - private void processConcurrencyThreadGroup(Element concurrencyThreadGroup) { - String testname = concurrencyThreadGroup.getAttribute("testname"); - concurrencyThreadGroup.setAttribute("testname", testname + "-" + context.getResourceIndex()); - if (concurrencyThreadGroup.getChildNodes().getLength() > 0) { - final NodeList childNodes = concurrencyThreadGroup.getChildNodes(); - for (int i = 0; i < childNodes.getLength(); i++) { - Node node = childNodes.item(i); - if (node instanceof Element) { - Element ele = (Element) node; - if (invalid(ele)) { - continue; - } - - if (nodeNameEquals(ele, STRING_PROP)) { - parseStringProp(ele); - } - } - } - } + private void processThreadGroupName(Element threadGroup) { + String testname = threadGroup.getAttribute("testname"); + threadGroup.setAttribute("testname", testname + "-" + context.getResourceIndex()); } private void processVariableThroughputTimer(Element variableThroughputTimer) { diff --git a/frontend/src/business/components/performance/report/components/PerformancePressureConfig.vue b/frontend/src/business/components/performance/report/components/PerformancePressureConfig.vue index 456f61e8d3..ee34766b45 100644 --- a/frontend/src/business/components/performance/report/components/PerformancePressureConfig.vue +++ b/frontend/src/business/components/performance/report/components/PerformancePressureConfig.vue @@ -21,45 +21,85 @@ size="mini"/>
- - + + + {{ $t('load_test.by_duration') }} + {{ $t('load_test.by_iteration') }} +
- - -   - - -
- - - - - - - +
+ + + +
+ + +   + + +
+ + + + + + + +
+
+ + + +
+ + +   + + +
+ + + + +
@@ -83,6 +123,10 @@ const STEPS = "Steps"; const DURATION = "duration"; const RPS_LIMIT = "rpsLimit"; const RPS_LIMIT_ENABLE = "rpsLimitEnable"; +const THREAD_TYPE = "threadType"; +const ITERATE_NUM = "iterateNum"; +const ITERATE_RAMP_UP = "iterateRampUpTime"; + const hexToRgba = function (hex, opacity) { return 'rgba(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5)) + ',' + parseInt('0x' + hex.slice(5, 7)) + ',' + opacity + ')'; @@ -128,6 +172,9 @@ export default { case RAMP_UP: this.threadGroups[i].rampUpTime = item.value; break; + case ITERATE_RAMP_UP: + this.threadGroups[i].iterateRampUp = item.value; + break; case DURATION: if (item.unit) { this.threadGroups[i].duration = item.value; @@ -144,6 +191,12 @@ export default { case RPS_LIMIT_ENABLE: this.threadGroups[i].rpsLimitEnable = item.value; break; + case THREAD_TYPE: + this.threadGroups[i].threadType = item.value; + break; + case ITERATE_NUM: + this.threadGroups[i].iterateNum = item.value; + break; default: break; } @@ -157,6 +210,9 @@ export default { case RAMP_UP: this.threadGroups[0].rampUpTime = d.value; break; + case ITERATE_RAMP_UP: + this.threadGroups[0].iterateRampUp = d.value; + break; case DURATION: if (d.unit) { this.threadGroups[0].duration = d.value; @@ -173,6 +229,12 @@ export default { case RPS_LIMIT_ENABLE: this.threadGroups[0].rpsLimitEnable = d.value; break; + case THREAD_TYPE: + this.threadGroups[0].threadType = d.value; + break; + case ITERATE_NUM: + this.threadGroups[0].iterateNum = d.value; + break; default: break; } diff --git a/frontend/src/business/components/performance/test/EditPerformanceTest.vue b/frontend/src/business/components/performance/test/EditPerformanceTest.vue index 2e2769f1a1..8af0e56832 100644 --- a/frontend/src/business/components/performance/test/EditPerformanceTest.vue +++ b/frontend/src/business/components/performance/test/EditPerformanceTest.vue @@ -298,6 +298,9 @@ export default { tg.rampUpTime = tg.rampUpTime || 5; tg.step = tg.step || 5; tg.rpsLimit = tg.rpsLimit || 10; + tg.threadType = tg.threadType || 'DURATION'; + tg.iterateNum = tg.iterateNum || 1; + tg.iterateRampUp = tg.iterateRampUp || 10; handler.calculateChart(tg); }); } diff --git a/frontend/src/business/components/performance/test/components/PerformancePressureConfig.vue b/frontend/src/business/components/performance/test/components/PerformancePressureConfig.vue index c531212baa..88098c33af 100644 --- a/frontend/src/business/components/performance/test/components/PerformancePressureConfig.vue +++ b/frontend/src/business/components/performance/test/components/PerformancePressureConfig.vue @@ -33,45 +33,83 @@ size="mini"/>

- - + + + {{ $t('load_test.by_duration') }} + {{ $t('load_test.by_iteration') }} +
- - -   - - -
- - - - - - - +
+ + + +
+ + +   + + +
+ + + + + + + +
+
+ + + +
+ + +   + + +
+ + + + +
@@ -91,11 +129,14 @@ import {findTestPlan, findThreadGroup} from "@/business/components/performance/t const TARGET_LEVEL = "TargetLevel"; const RAMP_UP = "RampUp"; +const ITERATE_RAMP_UP = "iterateRampUpTime"; const STEPS = "Steps"; const DURATION = "duration"; const RPS_LIMIT = "rpsLimit"; const RPS_LIMIT_ENABLE = "rpsLimitEnable"; const HOLD = "Hold"; +const THREAD_TYPE = "threadType"; +const ITERATE_NUM = "iterateNum"; const hexToRgba = function (hex, opacity) { return 'rgba(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5)) + ',' @@ -186,6 +227,9 @@ export default { case RAMP_UP: this.threadGroups[i].rampUpTime = item.value; break; + case ITERATE_RAMP_UP: + this.threadGroups[i].iterateRampUp = item.value; + break; case DURATION: if (item.unit) { this.threadGroups[i].duration = item.value; @@ -202,9 +246,19 @@ export default { case RPS_LIMIT_ENABLE: this.threadGroups[i].rpsLimitEnable = item.value; break; + case THREAD_TYPE: + this.threadGroups[i].threadType = item.value; + break; + case ITERATE_NUM: + this.threadGroups[i].iterateNum = item.value; + break; default: break; } + // + this.$set(this.threadGroups[i], "threadType", this.threadGroups[i].threadType || 'DURATION'); + this.$set(this.threadGroups[i], "iterateNum", this.threadGroups[i].iterateNum || 1); + this.$set(this.threadGroups[i], "iterateRampUp", this.threadGroups[i].iterateRampUp || 10); }) this.calculateChart(this.threadGroups[i]); } else { @@ -215,6 +269,9 @@ export default { case RAMP_UP: this.threadGroups[0].rampUpTime = d.value; break; + case ITERATE_RAMP_UP: + this.threadGroups[0].iterateRampUp = d.value; + break; case DURATION: if (d.unit) { this.threadGroups[0].duration = d.value; @@ -231,9 +288,18 @@ export default { case RPS_LIMIT_ENABLE: this.threadGroups[0].rpsLimitEnable = d.value; break; + case THREAD_TYPE: + this.threadGroups[0].threadType = d.value; + break; + case ITERATE_NUM: + this.threadGroups[0].iterateNum = d.value; + break; default: break; } + this.$set(this.threadGroups[0], "threadType", this.threadGroups[0].threadType || 'DURATION'); + this.$set(this.threadGroups[0], "iterateNum", this.threadGroups[0].iterateNum || 1); + this.$set(this.threadGroups[0], "iterateRampUp", this.threadGroups[0].iterateRampUp || 10); this.calculateChart(this.threadGroups[0]); } } @@ -461,7 +527,7 @@ export default { for (let i = 0; i < this.threadGroups.length; i++) { if (!this.threadGroups[i].threadNumber || !this.threadGroups[i].duration - || !this.threadGroups[i].rampUpTime || !this.threadGroups[i].step) { + || !this.threadGroups[i].rampUpTime || !this.threadGroups[i].step || !this.threadGroups[i].iterateNum) { this.$warning(this.$t('load_test.pressure_config_params_is_empty')); this.$emit('changeActive', '1'); return false; @@ -488,6 +554,9 @@ export default { {key: RPS_LIMIT, value: this.threadGroups[i].rpsLimit}, {key: RPS_LIMIT_ENABLE, value: this.threadGroups[i].rpsLimitEnable}, {key: HOLD, value: this.threadGroups[i].duration - this.threadGroups[i].rampUpTime}, + {key: THREAD_TYPE, value: this.threadGroups[i].threadType}, + {key: ITERATE_NUM, value: this.threadGroups[i].iterateNum}, + {key: ITERATE_RAMP_UP, value: this.threadGroups[i].iterateRampUp}, ]); } return result; diff --git a/frontend/src/i18n/en-US.js b/frontend/src/i18n/en-US.js index 7ccfed91b5..8c6d8929f2 100644 --- a/frontend/src/i18n/en-US.js +++ b/frontend/src/i18n/en-US.js @@ -437,6 +437,10 @@ export default { input_rps_limit: 'Please enter a limit', ramp_up_time_within: 'In', ramp_up_time_minutes: 'seconds, separate', + ramp_up_time_seconds: 'seconds add concurrent users', + iterate_num: 'Iterations: ', + by_iteration: 'By iterations', + by_duration: 'By duration', ramp_up_time_times: 'add concurrent users', advanced_config_error: 'Advanced configuration verification failed', domain_bind: 'Domain bind', diff --git a/frontend/src/i18n/zh-CN.js b/frontend/src/i18n/zh-CN.js index 6a60c654b1..aa738060b6 100644 --- a/frontend/src/i18n/zh-CN.js +++ b/frontend/src/i18n/zh-CN.js @@ -433,6 +433,10 @@ export default { input_rps_limit: '请输入限制', ramp_up_time_within: '在', ramp_up_time_minutes: '秒内,分', + ramp_up_time_seconds: '秒内增加并发用户', + iterate_num: '迭代次数 (次): ', + by_iteration: '按迭代次数', + by_duration: '按持续时间', ramp_up_time_times: '次增加并发用户', advanced_config_error: '高级配置校验失败', domain_bind: '域名绑定', diff --git a/frontend/src/i18n/zh-TW.js b/frontend/src/i18n/zh-TW.js index 42a68bcd73..61dcb952f1 100644 --- a/frontend/src/i18n/zh-TW.js +++ b/frontend/src/i18n/zh-TW.js @@ -433,6 +433,10 @@ export default { input_rps_limit: '請輸入限制', ramp_up_time_within: '在', ramp_up_time_minutes: '秒內,分', + ramp_up_time_seconds: '秒內增加並發用戶', + iterate_num: '迭代次數 (次): ', + by_iteration: '按迭代次數', + by_duration: '按壓測時長', ramp_up_time_times: '次增加並發用戶', advanced_config_error: '高級配置校驗失敗', domain_bind: '域名綁定', From f9f5ff86bdb8c94b1807b94c0037d63052529fa6 Mon Sep 17 00:00:00 2001 From: chenjianxing Date: Wed, 6 Jan 2021 15:13:21 +0800 Subject: [PATCH 3/8] =?UTF-8?q?refactor:=20postman=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../metersphere/api/parse/ApiImportAbstractParser.java | 6 ++++-- .../java/io/metersphere/api/parse/PostmanParser.java | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/parse/ApiImportAbstractParser.java b/backend/src/main/java/io/metersphere/api/parse/ApiImportAbstractParser.java index e5f05b7805..27add6d374 100644 --- a/backend/src/main/java/io/metersphere/api/parse/ApiImportAbstractParser.java +++ b/backend/src/main/java/io/metersphere/api/parse/ApiImportAbstractParser.java @@ -65,10 +65,12 @@ public abstract class ApiImportAbstractParser implements ApiImportParser { protected ApiModule getSelectModule(String moduleId) { apiModuleService = CommonBeanFactory.getBean(ApiModuleService.class); - if (StringUtils.isNotBlank(moduleId)) { + if (StringUtils.isNotBlank(moduleId) && !StringUtils.equals("root", moduleId)) { ApiModule module = new ApiModule(); ApiModuleDTO moduleDTO = apiModuleService.getNode(moduleId); - BeanUtils.copyBean(module, moduleDTO); + if (moduleDTO != null) { + BeanUtils.copyBean(module, moduleDTO); + } return module; } return null; diff --git a/backend/src/main/java/io/metersphere/api/parse/PostmanParser.java b/backend/src/main/java/io/metersphere/api/parse/PostmanParser.java index 71d2379d3e..13d7f9b9f0 100644 --- a/backend/src/main/java/io/metersphere/api/parse/PostmanParser.java +++ b/backend/src/main/java/io/metersphere/api/parse/PostmanParser.java @@ -61,6 +61,16 @@ public class PostmanParser extends ApiImportAbstractParser { MsHTTPSamplerProxy request = buildRequest(requestItem.getName(), url.getRaw(), requestDesc.getMethod()); ApiDefinitionResult apiDefinition = buildApiDefinition(request.getId(), requestItem.getName(), url.getRaw(), requestDesc.getMethod()); + if (StringUtils.isNotBlank(request.getPath())) { + String path = request.getPath().split("\\?")[0]; + path = path.replace("{{", "${"); + path = path.replace("}}", "}"); + request.setPath(path); + apiDefinition.setPath(path); + } else { + request.setPath("/"); + apiDefinition.setPath("/"); + } parseBody(request.getBody(), requestDesc); request.setArguments(parseKeyValue(url.getQuery())); request.setHeaders(parseKeyValue(requestDesc.getHeader())); From 9bd8a55f2eebd8b01191d81d044789f80c6246b4 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 6 Jan 2021 15:26:47 +0800 Subject: [PATCH 4/8] chore: Sync --- backend/src/main/java/io/metersphere/xpack | 2 +- frontend/src/business/components/xpack | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/io/metersphere/xpack b/backend/src/main/java/io/metersphere/xpack index 9f4a9bbf46..068127ce59 160000 --- a/backend/src/main/java/io/metersphere/xpack +++ b/backend/src/main/java/io/metersphere/xpack @@ -1 +1 @@ -Subproject commit 9f4a9bbf46fc1333dbcccea21f83e27e3ec10b1f +Subproject commit 068127ce59ea8b016434ed52a9de4a7a4b13bdb4 diff --git a/frontend/src/business/components/xpack b/frontend/src/business/components/xpack index 010ad7a5f0..7d43154a7c 160000 --- a/frontend/src/business/components/xpack +++ b/frontend/src/business/components/xpack @@ -1 +1 @@ -Subproject commit 010ad7a5f072a5e9d368c756a2473bbd20781433 +Subproject commit 7d43154a7c19732407a8e9ace8a7d1ea13c91f36 From 92fb0310ea6e18bbb84b25ca0f489c89e0b099b6 Mon Sep 17 00:00:00 2001 From: ed <73098982+ed-NCC-1701-B@users.noreply.github.com> Date: Wed, 6 Jan 2021 15:45:24 +0800 Subject: [PATCH 5/8] Update README.md --- README.md | 412 +++++++++++++++++++++++++++++------------------------- 1 file changed, 219 insertions(+), 193 deletions(-) diff --git a/README.md b/README.md index 9048e41765..23dccbda7d 100755 --- a/README.md +++ b/README.md @@ -80,199 +80,225 @@ v1.1.0 是 v1.0.0 之后的功能版本。 ## 功能列表 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
测试跟踪项目管理多项目支持,测试用例、测试计划与项目关联
测试用例管理在线编辑用例
以树状形式展示项目的模块及其用例
自定义用例属性
快速导入用例到系统
测试用例评审基于已有用例发起评审
在线更新评审结果
支持多人在线添加评审评论
灵活的评审人分配形式
测试计划跟踪基于已有用例发起测试计划
在线更新用例执行结果
灵活的用例分配方式
在线生成测试报告,支持自定义测试报告模板
与平台中的接口测试、性能测试功能结合,自动更新关联用例的结果
记录测试用例关联的缺陷
缺陷记录支持关联到 Jira/TAPD 平台
测试报告支持分享、导出
接口测试测试脚本在线编辑接口测试内容
支持参数化测试
灵活多样的断言支持
支持多接口的场景化测试
测试场景复用
测试场景支持引用已有环境信息
测试环境信息管理
通过浏览器插件快速录制测试脚本
支持前后置 BeanShell/Python 脚本
上传并引用自定义 Jar 包
多协议支持,支持 HTTP、Dubbo、SQL、TCP 类型请求
支持等待时间、条件判断等逻辑控制功能
测试执行内置定时任务支持
通过 Jenkins 插件触发测试执行
多个接口测试一键合并执行
一键创建性能测试
测试报告测试执行后自动生成动态实时测试报告
测试报告导出
通过邮件、IM 工具等通知执行结果
性能测试测试脚本完全兼容 JMeter 脚本
在线调整压力参数
分布式压力测试
支持参数化测试
通过浏览器插件快速录制测试脚本
多协议支持
测试执行内置定时任务支持
通过 Jenkins 插件触发测试执行
测试报告测试执行后自动生成测试报告
丰富的测试报告展现形式
测试报告导出
查看测试日志详情
系统管理用户租户管理支持多级租户体系
支持多种租户角色
LDAP 认证对接
测试资源管理性能测试资源池管理
消息通知配置IM 工具通知(如企业微信、钉钉)
邮件通知配置
集成与扩展完善的 API 列表
支持对接 Jenkins 等持续集成工具
支持对接 Jira/TAPD 等缺陷管理工具
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
测试跟踪测试用例管理在线编辑用例
编辑窗口支持上传附件
查看与编辑窗口显示评审评论
以树状形式展示项目的模块及其用例
支持测试用例模块树拖拽排序
自定义用例等级/用例类型/测试方式
支持Excel/Xmind格式快速导入用例到系统
支持Excel格式快速导出用例到本地
测试用例评审基于已有用例发起评审
支持添加多个评审人
在线更新评审结果
支持多人在线添加评审评论
测试计划跟踪基于已有用例发起测试计划
支持在线更新用例执行结果
在线生成测试报告,支持自定义测试报告模板
与平台中的接口测试、性能测试功能联动,自动更新关联用例的结果
记录测试用例关联的缺陷
缺陷记录支持关联到 Jira/TAPD
支持PDF格式测试报告导出
接口测试接口定义在线编辑接口测试内容
支持 HTTP/Dubbo/SQL/TCP 类型接口请求
支持接口快捷调制
支持接口列表和用例列表切换显示
支持用例编辑窗口正则/jsonpath/Xpath等多种类型的断言规则
支持用例编辑窗口正则/jsonpath/Xpath类型的参数提取
支持用例编辑窗口前后置 BeanShell/Python 脚本
测试环境信息管理
支持单接口测试引用环境信息
支持通过浏览器插件快速录制测试脚本
支持Metersphere json/Postman/Swagger格式快速导入用例到系统
支持Metersphere json格式快速导出用例到本地
支持上传并引用自定义 Jar 包
接口自动化创建多接口的场景化测试
支持自定义场景标签
支持多层级场景嵌套结构
支持接口列表快速导入测试场景
支持测试场景复用
支持添加自定义请求/自定义脚本
支持添加等待时间/条件判断等多类型逻辑控制器
场景调试支持引用已有环境信息
支持定时任务
支持通过 Jenkins 插件触发测试执行
Jenkins 插件支持 Pipeline 方式调用
支持一键创建性能测试
测试报告测试执行后自动生成测试报告
支持PDF格式测试报告导出
性能测试性能测试脚本支持上传JMX/CSV/JAR格式文件创建性能测试
支持分线程组配置压力参数
支持下载 JTL 文件
支持通过浏览器插件快速录制测试脚本
测试执行内置定时任务支持
支持通过 Jenkins 插件触发测试执行
测试报告测试执行后自动生成动态实时测试报告
支持PDF格式测试报告导出
系统管理用户租户管理支持多级租户体系
支持多种租户角色
支持LDAP 认证对接
测试资源管理性能测试资源池管理
消息通知配置支持企业微信/钉钉等多种IM 工具通知配置
支持邮件通知配置
集成与扩展配置API 列表
支持对接 Jenkins 等持续集成工具
支持对接 Jira/TAPD 等缺陷管理工具
项目管理多项目支持,测试用例、测试计划与项目关联
+ 详细的版本规划请参考 [版本路线图](https://github.com/metersphere/metersphere/blob/master/ROADMAP.md) From c6ceea74abdc3ac20be0121d0f0fdfc5bab1b8a0 Mon Sep 17 00:00:00 2001 From: "Captain.B" Date: Wed, 6 Jan 2021 16:14:11 +0800 Subject: [PATCH 6/8] =?UTF-8?q?refactor:=20=E8=AE=BE=E7=BD=AErps=E6=97=B6?= =?UTF-8?q?=E9=95=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../parse/xml/reader/jmx/JmeterDocumentParser.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java b/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java index 3a07da3ca2..988c61955e 100644 --- a/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java +++ b/backend/src/main/java/io/metersphere/performance/parse/xml/reader/jmx/JmeterDocumentParser.java @@ -972,15 +972,8 @@ public class JmeterDocumentParser implements DocumentParser { } private void processVariableThroughputTimer(Element variableThroughputTimer) { - Object durations = context.getProperty("duration"); - Integer duration; - if (durations instanceof List) { - Object o = ((List) durations).get(0); - duration = (Integer) o; - ((List) durations).remove(0); - } else { - duration = (Integer) durations; - } + // 设置rps时长 + Integer duration = Integer.MAX_VALUE; Object rpsLimits = context.getProperty("rpsLimit"); String rpsLimit; if (rpsLimits instanceof List) { From 9cb5c649255469c59461de6753430f53ccc65641 Mon Sep 17 00:00:00 2001 From: chenjianxing Date: Wed, 6 Jan 2021 16:21:26 +0800 Subject: [PATCH 7/8] refactor: v1.6 --- .../io/metersphere/api/parse/MsParser.java | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/parse/MsParser.java b/backend/src/main/java/io/metersphere/api/parse/MsParser.java index 0838445d12..9f700f6501 100644 --- a/backend/src/main/java/io/metersphere/api/parse/MsParser.java +++ b/backend/src/main/java/io/metersphere/api/parse/MsParser.java @@ -6,7 +6,6 @@ import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.Feature; import io.metersphere.api.dto.ApiTestImportRequest; import io.metersphere.api.dto.definition.ApiDefinitionResult; -import io.metersphere.api.dto.definition.ApiModuleDTO; import io.metersphere.api.dto.definition.parse.ApiDefinitionImport; import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy; import io.metersphere.api.dto.scenario.Body; @@ -15,10 +14,13 @@ import io.metersphere.api.dto.scenario.request.RequestType; import io.metersphere.api.service.ApiModuleService; import io.metersphere.base.domain.ApiModule; import io.metersphere.commons.utils.CommonBeanFactory; +import io.metersphere.commons.utils.LogUtil; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; import java.util.*; public class MsParser extends ApiImportAbstractParser { @@ -77,9 +79,9 @@ public class MsParser extends ApiImportAbstractParser { ApiDefinitionResult apiDefinition = buildApiDefinition(request.getId(), requestName, path, method); apiDefinition.setModuleId(module.getId()); apiDefinition.setProjectId(this.projectId); - parseBody(requestObject, request.getBody()); parseHeader(requestObject, request.getHeaders()); + parsePath(request, apiDefinition); apiDefinition.setRequest(JSONObject.toJSONString(request)); results.add(apiDefinition); }); @@ -87,6 +89,36 @@ public class MsParser extends ApiImportAbstractParser { return apiImport; } + private void parsePath(MsHTTPSamplerProxy request, ApiDefinitionResult apiDefinition) { + if (StringUtils.isNotBlank(request.getPath())) { + String[] split = request.getPath().split("\\?"); + String path = split[0]; + parseQueryParameters(split, request.getArguments()); + request.setPath(path); + apiDefinition.setPath(path); + } else { + request.setPath("/"); + apiDefinition.setPath("/"); + } + apiDefinition.setName(apiDefinition.getPath() + " [" + apiDefinition.getMethod() + "]"); + } + + private void parseQueryParameters(String[] split, List arguments) { + if (split.length > 1) { + try { + String queryParams = split[1]; + queryParams = URLDecoder.decode(queryParams, "UTF-8"); + String[] params = queryParams.split("&"); + for (String param : params) { + String[] kv = param.split("="); + arguments.add(new KeyValue(kv[0], kv[1])); + } + } catch (UnsupportedEncodingException e) { + LogUtil.info(e.getMessage(), e); + return; + } + } + } private void parseHeader(JSONObject requestObject, List msHeaders) { JSONArray headers = requestObject.getJSONArray("headers"); if (CollectionUtils.isNotEmpty(headers)) { From 1566f8c79b3f515e246060a0a3cc4f3d51ac0b74 Mon Sep 17 00:00:00 2001 From: chenjianxing Date: Wed, 6 Jan 2021 16:31:27 +0800 Subject: [PATCH 8/8] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/service/ApiModuleService.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/io/metersphere/api/service/ApiModuleService.java b/backend/src/main/java/io/metersphere/api/service/ApiModuleService.java index 7c3ab4f710..7ef25fb2de 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiModuleService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiModuleService.java @@ -213,20 +213,22 @@ public class ApiModuleService extends NodeTreeService { public int editNode(DragModuleRequest request) { request.setUpdateTime(System.currentTimeMillis()); checkApiModuleExist(request); - List apiModule = queryByModuleIds(request.getNodeIds()); + List apiDefinitionResults = queryByModuleIds(request.getNodeIds()); - apiModule.forEach(apiDefinition -> { - StringBuilder path = new StringBuilder(apiDefinition.getModulePath()); - List pathLists = Arrays.asList(path.toString().split("/")); - pathLists.set(request.getLevel(), request.getName()); - path.delete(0, path.length()); - for (int i = 1; i < pathLists.size(); i++) { - path = path.append("/").append(pathLists.get(i)); + apiDefinitionResults.forEach(apiDefinition -> { + if (StringUtils.isNotBlank(apiDefinition.getModulePath())) { + StringBuilder path = new StringBuilder(apiDefinition.getModulePath()); + List pathLists = Arrays.asList(path.toString().split("/")); + pathLists.set(request.getLevel(), request.getName()); + path.delete(0, path.length()); + for (int i = 1; i < pathLists.size(); i++) { + path = path.append("/").append(pathLists.get(i)); + } + apiDefinition.setModulePath(path.toString()); } - apiDefinition.setModulePath(path.toString()); }); - batchUpdateApiDefinition(apiModule); + batchUpdateApiDefinition(apiDefinitionResults); return apiModuleMapper.updateByPrimaryKeySelective(request); }