Merge remote-tracking branch 'origin/master'

# Conflicts:
#	frontend/src/business/components/common/components/MsScheduleEdit.vue
This commit is contained in:
wenyann 2020-09-03 10:36:44 +08:00
commit 3e14fd3d6b
23 changed files with 454 additions and 220 deletions

1
.gitignore vendored
View File

@ -30,3 +30,4 @@ target
.settings
.project
.classpath
.jython_cache

View File

@ -153,6 +153,12 @@
</exclusions>
</dependency>
<dependency>
<groupId>org.python</groupId>
<artifactId>jython</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_functions</artifactId>

View File

@ -0,0 +1,10 @@
package io.metersphere.api.dto.scenario.processor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class JSR223PostProcessor extends JSR223Processor {
}

View File

@ -0,0 +1,10 @@
package io.metersphere.api.dto.scenario.processor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class JSR223PreProcessor extends JSR223Processor {
}

View File

@ -0,0 +1,9 @@
package io.metersphere.api.dto.scenario.processor;
import lombok.Data;
@Data
public class JSR223Processor {
private String script;
private String language;
}

View File

@ -8,6 +8,8 @@ import io.metersphere.api.dto.scenario.assertions.Assertions;
import io.metersphere.api.dto.scenario.extract.Extract;
import io.metersphere.api.dto.scenario.processor.BeanShellPostProcessor;
import io.metersphere.api.dto.scenario.processor.BeanShellPreProcessor;
import io.metersphere.api.dto.scenario.processor.JSR223PostProcessor;
import io.metersphere.api.dto.scenario.processor.JSR223PreProcessor;
import io.metersphere.api.dto.scenario.request.dubbo.ConfigCenter;
import io.metersphere.api.dto.scenario.request.dubbo.ConsumerAndService;
import io.metersphere.api.dto.scenario.request.dubbo.RegistryCenter;
@ -51,4 +53,8 @@ public class DubboRequest implements Request {
private BeanShellPostProcessor beanShellPostProcessor;
@JSONField(ordinal = 14)
private Boolean enable;
@JSONField(ordinal = 15)
private JSR223PreProcessor jsr223PreProcessor;
@JSONField(ordinal = 16)
private JSR223PostProcessor jsr223PostProcessor;
}

View File

@ -8,6 +8,8 @@ import io.metersphere.api.dto.scenario.assertions.Assertions;
import io.metersphere.api.dto.scenario.extract.Extract;
import io.metersphere.api.dto.scenario.processor.BeanShellPostProcessor;
import io.metersphere.api.dto.scenario.processor.BeanShellPreProcessor;
import io.metersphere.api.dto.scenario.processor.JSR223PostProcessor;
import io.metersphere.api.dto.scenario.processor.JSR223PreProcessor;
import lombok.Data;
import java.util.List;
@ -48,5 +50,9 @@ public class HttpRequest implements Request {
@JSONField(ordinal = 15)
private Long responseTimeout;
@JSONField(ordinal = 16)
private Boolean followRedirects;;
private Boolean followRedirects;
@JSONField(ordinal = 17)
private JSR223PreProcessor jsr223PreProcessor;
@JSONField(ordinal = 18)
private JSR223PostProcessor jsr223PostProcessor;
}

View File

@ -11,6 +11,8 @@ import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.visualizers.backend.BackendListener;
import org.apache.jorphan.collections.HashTree;
import org.python.core.Options;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@ -30,6 +32,12 @@ public class JMeterService {
String JMETER_PROPERTIES = JMETER_HOME + "/bin/jmeter.properties";
JMeterUtils.loadJMeterProperties(JMETER_PROPERTIES);
JMeterUtils.setJMeterHome(JMETER_HOME);
JMeterUtils.setLocale(LocaleContextHolder.getLocale());
//解决无法加载 PyScriptEngineFactory
Options.importSite = false;
try {
Object scriptWrapper = SaveService.loadElement(is);
HashTree testPlan = getHashTree(scriptWrapper);

View File

@ -0,0 +1,100 @@
/*
* 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.util;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.ListResourceBundle;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ResourceBundle;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import org.apache.jmeter.testbeans.TestBean;
/**
* 解决JSR233加载 ScriptEngineFactory 空指针问题
*/
public abstract class JSR223BeanInfoSupport extends ScriptingBeanInfoSupport {
private static final String[] LANGUAGE_TAGS;
/**
* Will be removed in next version following 3.2
* @deprecated use {@link JSR223BeanInfoSupport#getLanguageNames()}
*/
@Deprecated
public static final String[][] LANGUAGE_NAMES; // NOSONAR Kept for backward compatibility
private static final String[][] CONSTANT_LANGUAGE_NAMES;
static {
Map<String, ScriptEngineFactory> nameMap = new HashMap<>();
ScriptEngineManager sem = new ScriptEngineManager();
final List<ScriptEngineFactory> engineFactories = sem.getEngineFactories();
for(ScriptEngineFactory fact : engineFactories){
List<String> names = fact.getNames();
for(String shortName : names) {
if (shortName != null) {
nameMap.put(shortName.toLowerCase(Locale.ENGLISH), fact);
}
}
}
LANGUAGE_TAGS = nameMap.keySet().toArray(new String[nameMap.size()]);
Arrays.sort(LANGUAGE_TAGS);
CONSTANT_LANGUAGE_NAMES = new String[nameMap.size()][2];
int i = 0;
for(Entry<String, ScriptEngineFactory> me : nameMap.entrySet()) {
final String key = me.getKey();
CONSTANT_LANGUAGE_NAMES[i][0] = key;
final ScriptEngineFactory fact = me.getValue();
CONSTANT_LANGUAGE_NAMES[i++][1] = key +
" (" // $NON-NLS-1$
+ fact.getLanguageName() + " " + fact.getLanguageVersion() // $NON-NLS-1$
+ " / " // $NON-NLS-1$
+ fact.getEngineName() + " " + fact.getEngineVersion() // $NON-NLS-1$
+ ")"; // $NON-NLS-1$
}
LANGUAGE_NAMES = getLanguageNames(); // NOSONAR Kept for backward compatibility
}
private static final ResourceBundle NAME_BUNDLE = new ListResourceBundle() {
@Override
protected Object[][] getContents() {
return CONSTANT_LANGUAGE_NAMES;
}
};
protected JSR223BeanInfoSupport(Class<? extends TestBean> beanClass) {
super(beanClass, LANGUAGE_TAGS, NAME_BUNDLE);
}
/**
* @return String array of 2 columns array containing Script engine short name / Script Language details
*/
public static final String[][] getLanguageNames() {
return CONSTANT_LANGUAGE_NAMES.clone();
}
}

View File

@ -87,14 +87,18 @@ export default {
}
}
},
mounted() {
let self = this;
methods: {
registerEvents() {
ApiEvent.$on(LIST_CHANGE, () => {
self.$refs.projectRecent.recent();
self.$refs.testRecent.recent();
self.$refs.reportRecent.recent();
this.$refs.projectRecent.recent();
this.$refs.testRecent.recent();
this.$refs.reportRecent.recent();
});
}
},
mounted() {
this.registerEvents();
}
}
</script>

View File

@ -166,7 +166,12 @@ export default {
});
this.scenarios.forEach(scenario => {
if (scenario.environmentId) {
scenario.environment = environmentMap.get(scenario.environmentId);
let env = environmentMap.get(scenario.environmentId);
if (!env) {
scenario.environmentId = undefined;
} else {
scenario.environment = env;
}
}
});
});

View File

@ -2,9 +2,10 @@
<div >
<el-row>
<el-col :span="20" class="script-content">
<ms-code-edit v-if="isCodeEditAlive" mode="java" :read-only="isReadOnly" :data.sync="beanShellProcessor.script" theme="eclipse" :modes="['java']" ref="codeEdit"/>
<ms-code-edit v-if="isCodeEditAlive" :mode="codeEditModeMap[jsr223Processor.language]" :read-only="isReadOnly" :data.sync="jsr223Processor.script" theme="eclipse" :modes="['java','python']" ref="codeEdit"/>
</el-col>
<el-col :span="4" class="script-index">
<ms-dropdown :default-command="jsr223Processor.language" :commands="languages" @command="languageChange"/>
<div class="template-title">{{$t('api_test.request.processor.code_template')}}</div>
<div v-for="(template, index) in codeTemplates" :key="index" class="code-template">
<el-link :disabled="template.disabled" @click="addTemplate(template)">{{template.title}}</el-link>
@ -21,9 +22,13 @@
<script>
import MsCodeEdit from "../../../../common/components/MsCodeEdit";
import MsInstructionsIcon from "../../../../common/components/MsInstructionsIcon";
import MsDropdown from "../../../../common/components/MsDropdown";
export default {
name: "MsBeanShellProcessor",
components: {MsInstructionsIcon, MsCodeEdit},
name: "MsJsr233Processor",
components: {MsDropdown, MsInstructionsIcon, MsCodeEdit},
data() {
return {
codeTemplates: [
@ -51,7 +56,14 @@
disabled: this.isPreProcessor
}
],
isCodeEditAlive: true
isCodeEditAlive: true,
languages: [
'beanshell',"python"
],
codeEditModeMap: {
beanshell: 'java',
python: 'python'
}
}
},
props: {
@ -62,7 +74,7 @@
type: Boolean,
default: false
},
beanShellProcessor: {
jsr223Processor: {
type: Object,
},
isPreProcessor: {
@ -72,15 +84,18 @@
},
methods: {
addTemplate(template) {
if (!this.beanShellProcessor.script) {
this.beanShellProcessor.script = "";
if (!this.jsr223Processor.script) {
this.jsr223Processor.script = "";
}
this.beanShellProcessor.script += template.value;
this.jsr223Processor.script += template.value;
this.reload();
},
reload() {
this.isCodeEditAlive = false;
this.$nextTick(() => (this.isCodeEditAlive = true));
},
languageChange(language) {
this.jsr223Processor.language = language;
}
}
}
@ -100,13 +115,10 @@
padding: 0 20px;
}
.script-index div:first-child {
font-weight: bold;
font-size: 15px;
}
.template-title {
margin-bottom: 5px;
font-weight: bold;
font-size: 15px;
}
.document-url {
@ -117,4 +129,8 @@
margin-left: 5px;
}
.ms-dropdown {
margin-bottom: 20px;
}
</style>

View File

@ -45,10 +45,10 @@
<ms-api-extract :is-read-only="isReadOnly" :extract="request.extract"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.request.processor.pre_exec_script')" name="beanShellPreProcessor">
<ms-bean-shell-processor :is-read-only="isReadOnly" :bean-shell-processor="request.beanShellPreProcessor"/>
<ms-jsr233-processor :is-read-only="isReadOnly" :jsr223-processor="request.jsr223PreProcessor"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.request.processor.post_exec_script')" name="beanShellPostProcessor">
<ms-bean-shell-processor :is-read-only="isReadOnly" :bean-shell-processor="request.beanShellPostProcessor"/>
<ms-jsr233-processor :is-read-only="isReadOnly" :jsr223-processor="request.jsr223PostProcessor"/>
</el-tab-pane>
</el-tabs>
</el-form>
@ -65,12 +65,12 @@
import MsDubboRegistryCenter from "@/business/components/api/test/components/request/dubbo/RegistryCenter";
import MsDubboConfigCenter from "@/business/components/api/test/components/request/dubbo/ConfigCenter";
import MsDubboConsumerService from "@/business/components/api/test/components/request/dubbo/ConsumerAndService";
import MsBeanShellProcessor from "../processor/BeanShellProcessor";
import MsJsr233Processor from "../processor/Jsr233Processor";
export default {
name: "MsApiDubboRequestForm",
components: {
MsBeanShellProcessor,
MsJsr233Processor,
MsDubboConsumerService,
MsDubboConfigCenter,
MsDubboRegistryCenter,

View File

@ -68,11 +68,11 @@
<el-tab-pane :label="$t('api_test.request.extract.label')" name="extract">
<ms-api-extract :is-read-only="isReadOnly" :extract="request.extract"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.request.processor.pre_exec_script')" name="beanShellPreProcessor">
<ms-bean-shell-processor :is-pre-processor="true" :is-read-only="isReadOnly" :bean-shell-processor="request.beanShellPreProcessor"/>
<el-tab-pane :label="$t('api_test.request.processor.pre_exec_script')" name="jsr223PreProcessor">
<ms-jsr233-processor :is-read-only="isReadOnly" :jsr223-processor="request.jsr223PreProcessor"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.request.processor.post_exec_script')" name="beanShellPostProcessor">
<ms-bean-shell-processor :is-read-only="isReadOnly" :bean-shell-processor="request.beanShellPostProcessor"/>
<el-tab-pane :label="$t('api_test.request.processor.post_exec_script')" name="jsr223PostProcessor">
<ms-jsr233-processor :is-read-only="isReadOnly" :jsr223-processor="request.jsr223PostProcessor"/>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.request.timeout_config')" name="advancedConfig">
<ms-api-advanced-config :is-read-only="isReadOnly" :request="request"/>
@ -90,14 +90,14 @@ import MsApiExtract from "../extract/ApiExtract";
import ApiRequestMethodSelect from "../collapse/ApiRequestMethodSelect";
import {REQUEST_HEADERS} from "@/common/js/constants";
import MsApiVariable from "@/business/components/api/test/components/ApiVariable";
import MsBeanShellProcessor from "../processor/BeanShellProcessor";
import MsJsr233Processor from "../processor/Jsr233Processor";
import MsApiAdvancedConfig from "../ApiAdvancedConfig";
export default {
name: "MsApiHttpRequestForm",
components: {
MsJsr233Processor,
MsApiAdvancedConfig,
MsBeanShellProcessor,
MsApiVariable, ApiRequestMethodSelect, MsApiExtract, MsApiAssertions, MsApiBody, MsApiKeyValue},
props: {
request: HttpRequest,

View File

@ -55,6 +55,15 @@ export default {
this.getReport();
}
},
mounted() {
// beanshell
if (!this.request.jsr223PreProcessor.script && this.request.beanShellPreProcessor) {
this.request.jsr223PreProcessor = this.request.beanShellPreProcessor;
}
if (!this.request.jsr223PostProcessor.script && this.request.beanShellPostProcessor) {
this.request.jsr223PostProcessor = this.request.beanShellPostProcessor;
}
},
methods: {
getReport() {
if (this.debugReportId) {

View File

@ -443,6 +443,30 @@ export class BeanShellProcessor extends DefaultTestElement {
}
}
export class JSR223Processor extends DefaultTestElement {
constructor(tag, guiclass, testclass, testname, processor) {
super(tag, guiclass, testclass, testname);
this.processor = processor || {};
this.stringProp('cacheKey', 'true');
this.stringProp('filename');
this.stringProp('parameters');
this.stringProp('script', this.processor.script);
this.stringProp('scriptLanguage', this.processor.language);
}
}
export class JSR223PreProcessor extends JSR223Processor {
constructor(testName, processor) {
super('JSR223PreProcessor', 'TestBeanGUI', 'JSR223PreProcessor', testName, processor)
}
}
export class JSR223PostProcessor extends JSR223Processor {
constructor(testName, processor) {
super('JSR223PostProcessor', 'TestBeanGUI', 'JSR223PostProcessor', testName, processor)
}
}
export class BeanShellPreProcessor extends BeanShellProcessor {
constructor(testName, processor) {
super('BeanShellPreProcessor', 'TestBeanGUI', 'BeanShellPreProcessor', testName, processor)

View File

@ -9,7 +9,7 @@ import {
HTTPSamplerArguments, HTTPsamplerFiles,
HTTPSamplerProxy,
JSONPathAssertion,
JSONPostProcessor,
JSONPostProcessor, JSR223PostProcessor, JSR223PreProcessor,
RegexExtractor,
ResponseCodeAssertion,
ResponseDataAssertion,
@ -309,6 +309,8 @@ export class HttpRequest extends Request {
this.debugReport = undefined;
this.beanShellPreProcessor = undefined;
this.beanShellPostProcessor = undefined;
this.jsr223PreProcessor = undefined;
this.jsr223PostProcessor = undefined;
this.enable = true;
this.connectTimeout = 60*1000;
this.responseTimeout = undefined;
@ -324,8 +326,8 @@ export class HttpRequest extends Request {
options.body = new Body(options.body);
options.assertions = new Assertions(options.assertions);
options.extract = new Extract(options.extract);
options.beanShellPreProcessor = new BeanShellProcessor(options.beanShellPreProcessor);
options.beanShellPostProcessor = new BeanShellProcessor(options.beanShellPostProcessor);
options.jsr223PreProcessor = new JSR223Processor(options.jsr223PreProcessor);
options.jsr223PostProcessor = new JSR223Processor(options.jsr223PostProcessor);
return options;
}
@ -394,7 +396,9 @@ export class DubboRequest extends Request {
this.debugReport = undefined;
this.beanShellPreProcessor = new BeanShellProcessor(options.beanShellPreProcessor);
this.beanShellPostProcessor = new BeanShellProcessor(options.beanShellPostProcessor);
this.enable = true;
this.enable = options.enable == undefined ? true : options.enable;
this.jsr223PreProcessor = new JSR223Processor(options.jsr223PreProcessor);
this.jsr223PostProcessor = new JSR223Processor(options.jsr223PostProcessor);
this.sets({args: KeyValue, attachmentArgs: KeyValue}, options);
}
@ -604,6 +608,16 @@ export class BeanShellProcessor extends BaseConfig {
}
}
export class JSR223Processor extends BaseConfig {
constructor(options) {
super();
this.script = undefined;
this.language = "beanshell";
this.set(options);
}
}
export class Text extends AssertionType {
constructor(options) {
super(ASSERTION_TYPE.TEXT);
@ -873,7 +887,7 @@ class JMXGenerator {
this.addRequestAssertion(sampler, request);
this.addBeanShellProcessor(sampler, request);
this.addJSR223PreProcessor(sampler, request);
threadGroup.put(sampler);
}
@ -940,13 +954,13 @@ class JMXGenerator {
}
}
addBeanShellProcessor(sampler, request) {
addJSR223PreProcessor(sampler, request) {
let name = request.name;
if (request.beanShellPreProcessor && request.beanShellPreProcessor.script) {
sampler.put(new BeanShellPreProcessor(name, request.beanShellPreProcessor));
if (request.jsr223PreProcessor && request.jsr223PreProcessor.script) {
sampler.put(new JSR223PreProcessor(name, request.jsr223PreProcessor));
}
if (request.beanShellPostProcessor && request.beanShellPostProcessor.script) {
sampler.put(new BeanShellPostProcessor(name, request.beanShellPostProcessor));
if (request.jsr223PostProcessor && request.jsr223PostProcessor.script) {
sampler.put(new JSR223PostProcessor(name, request.jsr223PostProcessor));
}
}

View File

@ -1,5 +1,5 @@
<template>
<el-dropdown @command="handleCommand">
<el-dropdown @command="handleCommand" class="ms-dropdown">
<slot>
<span class="el-dropdown-link">
{{currentCommand}}

View File

@ -95,14 +95,18 @@ export default {
}
}
},
mounted() {
let self = this;
methods: {
registerEvents() {
PerformanceEvent.$on(LIST_CHANGE, () => {
self.$refs.projectRecent.recent();
self.$refs.testRecent.recent();
self.$refs.reportRecent.recent();
this.$refs.projectRecent.recent();
this.$refs.testRecent.recent();
this.$refs.reportRecent.recent();
});
}
},
mounted() {
this.registerEvents();
}
}
</script>

View File

@ -100,12 +100,7 @@ export default {
},
mounted() {
this.init();
let self = this;
TrackEvent.$on(LIST_CHANGE, () => {
self.$refs.projectRecent.recent();
self.$refs.planRecent.recent();
self.$refs.caseRecent.recent();
});
this.registerEvents();
},
methods: {
reload() {
@ -132,6 +127,13 @@ export default {
this.testCaseEditPath = path;
this.reload();
}
},
registerEvents() {
TrackEvent.$on(LIST_CHANGE, () => {
this.$refs.projectRecent.recent();
this.$refs.planRecent.recent();
this.$refs.caseRecent.recent();
});
}
}
}

View File

@ -452,7 +452,7 @@ export default {
timeout_config: "超时设置",
connect_timeout: "连接超时",
response_timeout: "响应超时",
follow_redirects: "跟重定向",
follow_redirects: "跟重定向",
body_upload_limit_size: "上传文件大小不能超过 500 MB!",
assertions: {
label: "断言",