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 2cd73f9ccb..66f3588154 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -13,12 +13,10 @@ import io.metersphere.notice.service.MailService; import io.metersphere.notice.service.NoticeService; import io.metersphere.track.service.TestPlanTestCaseService; import org.apache.commons.lang3.StringUtils; -import org.apache.http.protocol.HTTP; import org.apache.jmeter.assertions.AssertionResult; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient; import org.apache.jmeter.visualizers.backend.BackendListenerContext; -import org.pac4j.core.context.HttpConstants; import org.springframework.http.HttpMethod; import java.io.Serializable; @@ -41,6 +39,12 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl private APIReportService apiReportService; + private TestPlanTestCaseService testPlanTestCaseService; + + private NoticeService noticeService; + + private MailService mailService; + public String runMode = ApiRunMode.RUN.name(); // 测试ID @@ -60,6 +64,18 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl if (apiReportService == null) { LogUtil.error("apiReportService is required"); } + testPlanTestCaseService = CommonBeanFactory.getBean(TestPlanTestCaseService.class); + if (testPlanTestCaseService == null) { + LogUtil.error("testPlanTestCaseService is required"); + } + noticeService = CommonBeanFactory.getBean(NoticeService.class); + if (noticeService == null) { + LogUtil.error("noticeService is required"); + } + mailService = CommonBeanFactory.getBean(MailService.class); + if (mailService == null) { + LogUtil.error("mailService is required"); + } super.setupTest(context); } @@ -112,7 +128,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl testResult.getScenarios().addAll(scenarios.values()); testResult.getScenarios().sort(Comparator.comparing(ScenarioResult::getId)); - ApiTestReport report = null; + ApiTestReport report; if (StringUtils.equals(this.runMode, ApiRunMode.DEBUG.name())) { report = apiReportService.get(debugReportId); } else { @@ -123,7 +139,6 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl queue.clear(); super.teardownTest(context); - TestPlanTestCaseService testPlanTestCaseService = CommonBeanFactory.getBean(TestPlanTestCaseService.class); List ids = testPlanTestCaseService.getTestPlanTestCaseIds(testResult.getTestId()); if (ids.size() > 0) { try { @@ -137,10 +152,8 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl } } - NoticeService noticeService = CommonBeanFactory.getBean(NoticeService.class); try { List noticeList = noticeService.queryNotice(testResult.getTestId()); - MailService mailService = CommonBeanFactory.getBean(MailService.class); mailService.sendApiNotification(report, noticeList); } catch (Exception e) { LogUtil.error(e); diff --git a/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java b/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java index f6bd0a53b8..fa8d36d982 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/JMeterService.java @@ -30,6 +30,8 @@ public class JMeterService { public void run(String testId, String debugReportId, InputStream is) { String JMETER_HOME = getJmeterHome(); + // 将当前类加载器设置为 loader ,解决由系统类加载器加载的 JMeter 无法动态加载 jar 包问题 + // 同时隔离在 beanshell 中访问由系统类加载器加载的其他类 NewDriver.setContextClassLoader(); String JMETER_PROPERTIES = JMETER_HOME + "/bin/jmeter.properties"; diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseMapper.xml index 251abaa9d1..53bb178dec 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestCaseMapper.xml @@ -99,7 +99,7 @@ select test_case.id, test_case.name, test_case.priority, test_case.type, test_case.review_status from test_case as test_case - left join test_plan_test_case as T2 on test_case.id=T2.case_id + left join test_plan_test_case as T2 on test_case.id=T2.case_id and T2.plan_id =#{request.planId} diff --git a/backend/src/main/java/io/metersphere/track/service/TestCaseNodeService.java b/backend/src/main/java/io/metersphere/track/service/TestCaseNodeService.java index f768dd2567..4c66a29c5f 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestCaseNodeService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestCaseNodeService.java @@ -542,7 +542,7 @@ public class TestCaseNodeService { rootPath = rootPath + rootNode.getName(); - if (level > 5) { + if (level > 8) { MSException.throwException(Translator.get("node_deep_limit")); } diff --git a/backend/src/main/java/io/metersphere/xpack b/backend/src/main/java/io/metersphere/xpack index ee74568be0..cf6b065263 160000 --- a/backend/src/main/java/io/metersphere/xpack +++ b/backend/src/main/java/io/metersphere/xpack @@ -1 +1 @@ -Subproject commit ee74568be0beba46da19616f5832e83f9164c688 +Subproject commit cf6b06526324326a563d933e07118fac014a63b4 diff --git a/backend/src/main/java/org/apache/jmeter/NewDriver.java b/backend/src/main/java/org/apache/jmeter/NewDriver.java index 32d383f3f0..04ce9a5937 100644 --- a/backend/src/main/java/org/apache/jmeter/NewDriver.java +++ b/backend/src/main/java/org/apache/jmeter/NewDriver.java @@ -71,25 +71,32 @@ public final class NewDriver { // Find JMeter home dir from the initial classpath String tmpDir; - StringTokenizer tok = new StringTokenizer(initiaClasspath, File.pathSeparator); - if (tok.countTokens() == 1 - || (tok.countTokens() == 2 // Java on Mac OS can add a second entry to the initial classpath - && OS_NAME_LC.startsWith("mac os x")// $NON-NLS-1$ - ) - ) { - File jar = new File(tok.nextToken()); - try { - tmpDir = jar.getCanonicalFile().getParentFile().getParent(); - } catch (IOException e) { - tmpDir = null; - } - } else {// e.g. started from IDE with full classpath + +// StringTokenizer tok = new StringTokenizer(initiaClasspath, File.pathSeparator); +// if (tok.countTokens() == 1 +// || (tok.countTokens() == 2 // Java on Mac OS can add a second entry to the initial classpath +// && OS_NAME_LC.startsWith("mac os x")// $NON-NLS-1$ +// ) +// ) { +// File jar = new File(tok.nextToken()); +// try { +// tmpDir = jar.getCanonicalFile().getParentFile().getParent(); +// System.out.println(tmpDir + "111"); +// } catch (IOException e) { +// tmpDir = null; +// } +// } else {// e.g. started from IDE with full classpath + + //只从 jmeter.home 加载 tmpDir = System.getProperty("jmeter.home","");// Allow override $NON-NLS-1$ $NON-NLS-2$ + System.out.println(System.getProperty("jmeter.home","")); + System.out.println(tmpDir + "222"); if (tmpDir.length() == 0) { File userDir = new File(System.getProperty("user.dir"));// $NON-NLS-1$ tmpDir = userDir.getAbsoluteFile().getParent(); + System.out.println(tmpDir + "333"); } - } +// } JMETER_INSTALLATION_DIRECTORY=tmpDir; /* diff --git a/backend/src/main/java/org/apache/jmeter/visualizers.backend/BackendListener.java b/backend/src/main/java/org/apache/jmeter/visualizers.backend/BackendListener.java new file mode 100644 index 0000000000..ad32e82415 --- /dev/null +++ b/backend/src/main/java/org/apache/jmeter/visualizers.backend/BackendListener.java @@ -0,0 +1,334 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by Fernflower decompiler) +// + +package org.apache.jmeter.visualizers.backend; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.LongAdder; +import java.util.concurrent.locks.LockSupport; +import org.apache.jmeter.config.Arguments; +import org.apache.jmeter.engine.util.NoThreadClone; +import org.apache.jmeter.samplers.Remoteable; +import org.apache.jmeter.samplers.SampleEvent; +import org.apache.jmeter.samplers.SampleListener; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.TestStateListener; +import org.apache.jmeter.testelement.property.TestElementProperty; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// 当前上下文类加载为 NewDriver 的类加载器,这里使用系统类加载器加载 APIBackendListenerClient + +public class BackendListener extends AbstractTestElement implements Backend, Serializable, SampleListener, TestStateListener, NoThreadClone, Remoteable { + private static final long serialVersionUID = 1L; + private static final Logger log = LoggerFactory.getLogger(BackendListener.class); + public static final String CLASSNAME = "classname"; + public static final String QUEUE_SIZE = "QUEUE_SIZE"; + private static final Object LOCK = new Object(); + public static final String ARGUMENTS = "arguments"; + private Class clientClass; + public static final String DEFAULT_QUEUE_SIZE = "5000"; + private static final transient SampleResult FINAL_SAMPLE_RESULT = new SampleResult(); + private static final Map queuesByTestElementName = new ConcurrentHashMap(); + private transient String myName; + private transient BackendListener.ListenerClientData listenerClientData; + + public BackendListener() { + this.setArguments(new Arguments()); + } + + public Object clone() { + BackendListener clone = (BackendListener)super.clone(); + clone.clientClass = this.clientClass; + return clone; + } + + private Class initClass() { + String name = this.getClassname().trim(); + + try { + // 当前上下文类加载为 NewDriver 的类加载器,这里使用系统类加载器加载 APIBackendListenerClient + return Class.forName(name, false, this.getClass().getClassLoader()); + } catch (Exception var3) { + log.error("{}\tException initialising: {}", new Object[]{this.whoAmI(), name, var3}); + return null; + } + } + + private String whoAmI() { + return Thread.currentThread().getName() + "@" + Integer.toHexString(this.hashCode()) + "-" + this.getName(); + } + + public void sampleOccurred(SampleEvent event) { + Arguments args = this.getArguments(); + BackendListenerContext context = new BackendListenerContext(args); + SampleResult sr = this.listenerClientData.client.createSampleResult(context, event.getResult()); + if (sr == null) { + if (log.isDebugEnabled()) { + log.debug("{} => Dropping SampleResult: {}", this.getName(), event.getResult()); + } + + } else { + try { + if (!this.listenerClientData.queue.offer(sr)) { + this.listenerClientData.queueWaits.add(1L); + long t1 = System.nanoTime(); + this.listenerClientData.queue.put(sr); + long t2 = System.nanoTime(); + this.listenerClientData.queueWaitTime.add(t2 - t1); + } + } catch (Exception var9) { + log.error("sampleOccurred, failed to queue the sample", var9); + } + + } + } + + private static void sendToListener(BackendListenerClient backendListenerClient, BackendListenerContext context, List sampleResults) { + if (!sampleResults.isEmpty()) { + backendListenerClient.handleSampleResults(sampleResults, context); + sampleResults.clear(); + } + + } + + private static BackendListenerClient createBackendListenerClientImpl(Class clientClass) { + if (clientClass == null) { + return new BackendListener.ErrorBackendListenerClient(); + } else { + try { + return (BackendListenerClient)clientClass.getDeclaredConstructor().newInstance(); + } catch (Exception var2) { + log.error("Exception creating: {}", clientClass, var2); + return new BackendListener.ErrorBackendListenerClient(); + } + } + } + + public void testStarted() { + this.testStarted("local"); + } + + public void testStarted(String host) { + if (log.isDebugEnabled()) { + log.debug("{}\ttestStarted({})", this.whoAmI(), host); + } + + String size = this.getQueueSize(); + + int queueSize; + try { + queueSize = Integer.parseInt(size); + } catch (NumberFormatException var12) { + log.warn("Invalid queue size '{}' defaulting to {}", size, "5000"); + queueSize = Integer.parseInt("5000"); + } + + Object var4 = LOCK; + synchronized(LOCK) { + this.myName = this.getName(); + this.listenerClientData = (BackendListener.ListenerClientData)queuesByTestElementName.get(this.myName); + if (this.listenerClientData == null) { + this.clientClass = this.initClass(); + BackendListenerClient backendListenerClient = createBackendListenerClientImpl(this.clientClass); + BackendListenerContext context = new BackendListenerContext((Arguments)this.getArguments().clone()); + this.listenerClientData = new BackendListener.ListenerClientData(); + this.listenerClientData.queue = new ArrayBlockingQueue(queueSize); + this.listenerClientData.queueWaits = new LongAdder(); + this.listenerClientData.queueWaitTime = new LongAdder(); + this.listenerClientData.latch = new CountDownLatch(1); + this.listenerClientData.client = backendListenerClient; + if (log.isInfoEnabled()) { + log.info("{}: Starting worker with class: {} and queue capacity: {}", new Object[]{this.getName(), this.clientClass, this.getQueueSize()}); + } + + BackendListener.Worker worker = new BackendListener.Worker(backendListenerClient, (Arguments)this.getArguments().clone(), this.listenerClientData); + worker.setDaemon(true); + worker.start(); + if (log.isInfoEnabled()) { + log.info("{}: Started worker with class: {}", this.getName(), this.clientClass); + } + + try { + backendListenerClient.setupTest(context); + } catch (Exception var10) { + throw new IllegalStateException("Failed calling setupTest", var10); + } + + queuesByTestElementName.put(this.myName, this.listenerClientData); + } + + this.listenerClientData.instanceCount++; + } + } + + public void testEnded(String host) { + Object var2 = LOCK; + synchronized(LOCK) { + BackendListener.ListenerClientData listenerClientDataForName = (BackendListener.ListenerClientData)queuesByTestElementName.get(this.myName); + if (log.isDebugEnabled()) { + log.debug("testEnded called on instance {}#{}", this.myName, listenerClientDataForName.instanceCount); + } + + if (listenerClientDataForName != null) { + listenerClientDataForName.instanceCount--; + if (listenerClientDataForName.instanceCount > 0) { + return; + } + + queuesByTestElementName.remove(this.myName); + } else { + log.error("No listener client data found for BackendListener {}", this.myName); + } + } + + try { + this.listenerClientData.queue.put(FINAL_SAMPLE_RESULT); + } catch (Exception var6) { + log.warn("testEnded() with exception: {}", var6.getMessage(), var6); + } + + if (this.listenerClientData.queueWaits.longValue() > 0L) { + log.warn("QueueWaits: {}; QueueWaitTime: {} (nanoseconds), you may need to increase queue capacity, see property 'backend_queue_capacity'", this.listenerClientData.queueWaits, this.listenerClientData.queueWaitTime); + } + + try { + this.listenerClientData.latch.await(); + BackendListenerContext context = new BackendListenerContext(this.getArguments()); + this.listenerClientData.client.teardownTest(context); + } catch (Exception var5) { + throw new IllegalStateException("Failed calling teardownTest", var5); + } + } + + public void testEnded() { + this.testEnded("local"); + } + + public void sampleStarted(SampleEvent e) { + } + + public void sampleStopped(SampleEvent e) { + } + + public void setArguments(Arguments args) { + args.removeArgument("useRegexpForSamplersList", "false"); + this.setProperty(new TestElementProperty("arguments", args)); + } + + public Arguments getArguments() { + return (Arguments)this.getProperty("arguments").getObjectValue(); + } + + public void setClassname(String classname) { + this.setProperty("classname", classname); + } + + public String getClassname() { + return this.getPropertyAsString("classname"); + } + + public void setQueueSize(String queueSize) { + this.setProperty("QUEUE_SIZE", queueSize, "5000"); + } + + public String getQueueSize() { + return this.getPropertyAsString("QUEUE_SIZE", "5000"); + } + + static class ErrorBackendListenerClient extends AbstractBackendListenerClient { + ErrorBackendListenerClient() { + } + + public void handleSampleResults(List sampleResults, BackendListenerContext context) { + BackendListener.log.warn("ErrorBackendListenerClient#handleSampleResult called, noop"); + Thread.yield(); + } + } + + private static final class Worker extends Thread { + private final BackendListener.ListenerClientData listenerClientData; + private final BackendListenerContext context; + private final BackendListenerClient backendListenerClient; + + private Worker(BackendListenerClient backendListenerClient, Arguments arguments, BackendListener.ListenerClientData listenerClientData) { + this.listenerClientData = listenerClientData; + arguments.addArgument("TestElement.name", this.getName()); + this.context = new BackendListenerContext(arguments); + this.backendListenerClient = backendListenerClient; + } + + public void run() { + boolean isDebugEnabled = BackendListener.log.isDebugEnabled(); + ArrayList sampleResults = new ArrayList(this.listenerClientData.queue.size()); + + try { + try { + boolean endOfLoop = false; + + while(!endOfLoop) { + if (isDebugEnabled) { + BackendListener.log.debug("Thread: {} taking SampleResult from queue: {}", Thread.currentThread().getName(), this.listenerClientData.queue.size()); + } + + SampleResult sampleResult = (SampleResult)this.listenerClientData.queue.take(); + if (isDebugEnabled) { + BackendListener.log.debug("Thread: {} took SampleResult: {}, isFinal: {}", new Object[]{Thread.currentThread().getName(), sampleResult, sampleResult == BackendListener.FINAL_SAMPLE_RESULT}); + } + + while(!(endOfLoop = sampleResult == BackendListener.FINAL_SAMPLE_RESULT) && sampleResult != null) { + sampleResults.add(sampleResult); + if (isDebugEnabled) { + BackendListener.log.debug("Thread: {} polling from queue: {}", Thread.currentThread().getName(), this.listenerClientData.queue.size()); + } + + sampleResult = (SampleResult)this.listenerClientData.queue.poll(); + if (isDebugEnabled) { + BackendListener.log.debug("Thread: {} took from queue: {}, isFinal: {}", new Object[]{Thread.currentThread().getName(), sampleResult, sampleResult == BackendListener.FINAL_SAMPLE_RESULT}); + } + } + + if (isDebugEnabled) { + BackendListener.log.debug("Thread: {} exiting with FINAL EVENT: {}, null: {}", new Object[]{Thread.currentThread().getName(), sampleResult == BackendListener.FINAL_SAMPLE_RESULT, sampleResult == null}); + } + + BackendListener.sendToListener(this.backendListenerClient, this.context, sampleResults); + if (!endOfLoop) { + LockSupport.parkNanos(100L); + } + } + } catch (InterruptedException var8) { + Thread.currentThread().interrupt(); + } + + BackendListener.sendToListener(this.backendListenerClient, this.context, sampleResults); + BackendListener.log.info("Worker ended"); + } finally { + this.listenerClientData.latch.countDown(); + } + + } + } + + private static final class ListenerClientData { + private BackendListenerClient client; + private BlockingQueue queue; + private LongAdder queueWaits; + private LongAdder queueWaitTime; + private int instanceCount; + private CountDownLatch latch; + + private ListenerClientData() { + } + } +} diff --git a/backend/src/main/resources/i18n/messages_en_US.properties b/backend/src/main/resources/i18n/messages_en_US.properties index 03340f13b2..ce776e5a0e 100644 --- a/backend/src/main/resources/i18n/messages_en_US.properties +++ b/backend/src/main/resources/i18n/messages_en_US.properties @@ -83,7 +83,7 @@ test_case_already_exists=The test case in this project is exists parse_data_error=Parse data error missing_header_information=Missing header information test_case_exist=A test case already exists under this project: -node_deep_limit=The node depth does not exceed 5 layers! +node_deep_limit=The node depth does not exceed 8 layers! before_delete_plan=There is an associated test case under this plan, please unlink it first! incorrect_format=\tincorrect format test_case_type_validate=\tmust be functional, performance, api diff --git a/backend/src/main/resources/i18n/messages_zh_CN.properties b/backend/src/main/resources/i18n/messages_zh_CN.properties index efc123244f..03f0f71158 100644 --- a/backend/src/main/resources/i18n/messages_zh_CN.properties +++ b/backend/src/main/resources/i18n/messages_zh_CN.properties @@ -83,7 +83,7 @@ test_case_already_exists=该项目下已存在该测试用例 parse_data_error=解析数据出错 missing_header_information=缺少头部信息 test_case_exist=该项目下已存在用例: -node_deep_limit=节点深度不超过5层! +node_deep_limit=节点深度不超过8层! before_delete_plan=该计划下存在关联测试用例,请先取消关联! incorrect_format=格式错误 test_case_type_validate=必须为functional、performance、api diff --git a/backend/src/main/resources/i18n/messages_zh_TW.properties b/backend/src/main/resources/i18n/messages_zh_TW.properties index 4916b0c676..f3df9dc820 100644 --- a/backend/src/main/resources/i18n/messages_zh_TW.properties +++ b/backend/src/main/resources/i18n/messages_zh_TW.properties @@ -83,7 +83,7 @@ test_case_already_exists=該項目下已存在該測試用例 parse_data_error=解析數據出錯 missing_header_information=缺少頭部信息 test_case_exist=該項目下已存在用例: -node_deep_limit=節點深度不超過5層! +node_deep_limit=節點深度不超過8層! before_delete_plan=該計劃下存在關聯測試用例,請先取消關聯! incorrect_format=格式錯誤 test_case_type_validate=必須為functional、performance、api diff --git a/frontend/src/business/components/api/test/model/ScenarioModel.js b/frontend/src/business/components/api/test/model/ScenarioModel.js index 8713ca1b5d..5865527d6c 100644 --- a/frontend/src/business/components/api/test/model/ScenarioModel.js +++ b/frontend/src/business/components/api/test/model/ScenarioModel.js @@ -555,14 +555,6 @@ export class TCPRequest extends Request { } isValid() { - if (this.enable) { - if (!this.server) { - return { - isValid: false, - info: 'api_test.request.tcp.server_cannot_be_empty' - } - } - } return { isValid: true } diff --git a/frontend/src/business/components/track/case/components/TestCaseEdit.vue b/frontend/src/business/components/track/case/components/TestCaseEdit.vue index 47534ead9f..ac1a3d9511 100644 --- a/frontend/src/business/components/track/case/components/TestCaseEdit.vue +++ b/frontend/src/business/components/track/case/components/TestCaseEdit.vue @@ -243,7 +243,7 @@ :label="$t('load_test.file_type')"> + :label="$t('test_track.case.upload_time')">