Merge remote-tracking branch 'origin/v1.1' into v1.1

This commit is contained in:
wenyann 2020-07-22 21:26:28 +08:00
commit 0e54e56c20
16 changed files with 237 additions and 32 deletions

View File

@ -6,11 +6,11 @@ ARG MS_VERSION=dev
RUN mkdir -p /opt/apps && mkdir -p /opt/jmeter RUN mkdir -p /opt/apps && mkdir -p /opt/jmeter
ADD backend/target/backend-1.0.jar /opt/apps ADD backend/target/backend-1.1.jar /opt/apps
ADD backend/target/classes/jmeter/ /opt/jmeter/ ADD backend/target/classes/jmeter/ /opt/jmeter/
ENV JAVA_APP_JAR=/opt/apps/backend-1.0.jar ENV JAVA_APP_JAR=/opt/apps/backend-1.1.jar
ENV AB_OFF=true ENV AB_OFF=true

2
README.md Normal file → Executable file
View File

@ -3,6 +3,8 @@
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/metersphere/metersphere)](https://github.com/metersphere/metersphere/releases/latest) [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/metersphere/metersphere)](https://github.com/metersphere/metersphere/releases/latest)
[![GitHub All Releases](https://img.shields.io/github/downloads/metersphere/metersphere/total)](https://github.com/metersphere/metersphere/releases) [![GitHub All Releases](https://img.shields.io/github/downloads/metersphere/metersphere/total)](https://github.com/metersphere/metersphere/releases)
> [English](README_EN.md) | 中文
MeterSphere 是一站式的开源企业级持续测试平台涵盖测试跟踪、接口测试、性能测试、团队协作等功能兼容JMeter 等开源标准,有效助力开发和测试团队充分利用云弹性进行高度可扩展的自动化测试,加速高质量软件的交付。 MeterSphere 是一站式的开源企业级持续测试平台涵盖测试跟踪、接口测试、性能测试、团队协作等功能兼容JMeter 等开源标准,有效助力开发和测试团队充分利用云弹性进行高度可扩展的自动化测试,加速高质量软件的交付。
- 测试跟踪: 远超 TestLink 的使用体验; - 测试跟踪: 远超 TestLink 的使用体验;

170
README_EN.md Executable file
View File

@ -0,0 +1,170 @@
# MeterSphere : Open-source Continuous Testing Platform
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/metersphere/metersphere)](https://github.com/metersphere/metersphere/releases/latest)
[![GitHub All Releases](https://img.shields.io/github/downloads/metersphere/metersphere/total)](https://github.com/metersphere/metersphere/releases)
> [中文](README.md) | English
MeterSphere is a one-stop open-source enterprise-class continuous testing platform. It covers functions such as tests tracking, interface testing, performance testing, team collaboration and is compatible with open-source standards such as JMeter. It helps development and testing teams to conduct highly scalable automated testing, making full use of elasticity of the cloud, and accelerating the delivery process of high-quality software.
- Test Tracking: Far beyond the user experience of TestLink.
- API Testing: Similar to Postman's experience.
- Performance Testing: Compatible with JMeter. Support Kubernetes and Cloud Environment. High concurrency, distributed performance testing with ease.
- Team Collaboration: duo-levels tenants system, naturally support team co-op.
## Quick Start
Only need two steps to install MeterSphere
What you need:
1. Prepare a 64-bit Linux host with no less than 8 G RAM
2. Log into root user and execute the command down below to install MeterSphere
```sh
curl -sSL https://github.com/metersphere/metersphere/releases/latest/download/quick_start.sh | sh
```
## Technical advantages
- Full Life Cycle: Full coverage over all STLC phases. Starting from the Test Plan to the Report Creation phase.
- Automation & Scalable: Automation for interface and performance testings. Fully adopt the elasticity of Cloud to produce a large scale of performance testing.
- Continuous Testing: Seamlessly integrated with the CI tools. Supporting enterprises for "Shift left" testing.
- Team Collaboration: Support different proportions of teams. Capable from a group of five to a testing center of several hundred people.
## Features List
<table>
<tbody>
<tr>
<td rowspan="10">Test Tracking</td>
<td>Project management</td>
<td>Multi-project support, test cases, test plan, and project association</td>
</tr>
<tr>
<td rowspan="4">Test Cases Management</td>
<td>Online editing test case</td>
</tr>
<tr>
<td>tree structure display project module and its test cases</td>
</tr>
<tr>
<td>Custom test case attributes</td>
</tr>
<tr>
<td>Quickly import test cases into the system</td>
</tr>
<tr>
<td rowspan="5">Test Plan Tracking</td>
<td>Initiate a test plan based on existing test cases</td>
</tr>
<tr>
<td>Online update of test case execution results</td>
</tr>
<tr>
<td>Flexible test case allocation</td>
</tr>
<tr>
<td>Generate test reports online, support custom test report templates</td>
</tr>
<tr>
<td>Combine with the interface test and performance test functions in the platform to automatically update the results of associated test cases</td>
</tr>
<tr>
<td rowspan="7">Interface Testing</td>
<td rowspan="5">Test Script</td>
<td>Online editing interface testing content</td>
</tr>
<tr>
<td>Support parameterized testing</td>
</tr>
<tr>
<td>Pliable assertion support</td>
</tr>
<tr>
<td>Support multi-interface scenario testing</td>
</tr>
<tr>
<td>Quickly record test script via brower plug-in</td>
</tr>
<tr>
<td rowspan="2">Test Report</td>
<td>Automatically generate test report after test execution</td>
</tr>
<tr>
<td>Exportable Test report</td>
</tr>
<tr>
<td rowspan="9">Performance Testing</td>
<td rowspan="5">Test Script</td>
<td>Fully compatible with JMeter script</td>
</tr>
<tr>
<td>Adjust pressure parameter online</td>
</tr>
<tr>
<td>Distributed pressure testing</td>
</tr>
<tr>
<td>Support parameterized testing</td>
</tr>
<tr>
<td>Quickly record test script via brower plug-in</td>
</tr>
<tr>
<td rowspan="4">Test Report</td>
<td>Automatically generate test report after test execution</td>
</tr>
<tr>
<td>Rich test report display form</td>
</tr>
<tr>
<td>Exportable test report</td>
</tr>
<tr>
<td>View test log details</td>
</tr>
<tr>
<td rowspan="6">System Management</td>
<td rowspan="2">Tenant management</td>
<td>Support multi-level tenant system</td>
</tr>
<tr>
<td>Support multiple tenant roles</td>
</tr>
<tr>
<td rowspan="2">Test resource management</td>
<td>Performance test resource pool management</td>
</tr>
<tr>
<td>Email notification configuration</td>
</tr>
<tr>
<td rowspan="2">Integration and expansion</td>
<td>Complete&nbsp;API&nbsp;list</td>
</tr>
<tr>
<td>Supports continuous integration tools such as&nbsp;Jenkins&nbsp;</td>
</tr>
</tbody>
</table>
## Technology stack
- Backend: [Spring Boot](https://www.tutorialspoint.com/spring_boot/spring_boot_introduction.htm)
- Frontend: [Vue.js](https://vuejs.org/)
- Middleware: [MySQL](https://www.mysql.com/), [Kafka](https://kafka.apache.org/)
- Basic infrastructure: [Docker](https://www.docker.com/), [Kubernetes](https://kubernetes.io/)
- Test engine: [JMeter](https://jmeter.apache.org/)
## License & Copyright
Copyright (c) 2014-2020 飞致云 FIT2CLOUD, All rights reserved.
Licensed under The GNU General Public License version 2 (GPLv2) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
https://www.gnu.org/licenses/gpl-2.0.html
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.

View File

@ -7,7 +7,7 @@
<parent> <parent>
<artifactId>metersphere-server</artifactId> <artifactId>metersphere-server</artifactId>
<groupId>io.metersphere</groupId> <groupId>io.metersphere</groupId>
<version>1.0</version> <version>1.1</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.ApiTestImportRequest; import io.metersphere.api.dto.ApiTestImportRequest;
import io.metersphere.api.dto.parse.ApiImport; import io.metersphere.api.dto.parse.ApiImport;
import io.metersphere.api.dto.scenario.request.RequestType;
import io.metersphere.commons.constants.MsRequestBodyType; import io.metersphere.commons.constants.MsRequestBodyType;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpMethod;
@ -38,6 +39,7 @@ public class MsParser extends ApiImportAbstractParser {
requestsObject.keySet().forEach(requestName -> { requestsObject.keySet().forEach(requestName -> {
JSONObject requestObject = requestsObject.getJSONObject(requestName); JSONObject requestObject = requestsObject.getJSONObject(requestName);
requestObject.put("name", requestName); requestObject.put("name", requestName);
requestObject.put("type", RequestType.HTTP);
JSONArray bodies = requestObject.getJSONArray("body"); JSONArray bodies = requestObject.getJSONArray("body");
if (StringUtils.equalsIgnoreCase(requestObject.getString("method"), HttpMethod.POST.name()) && bodies != null) { if (StringUtils.equalsIgnoreCase(requestObject.getString("method"), HttpMethod.POST.name()) && bodies != null) {
StringBuilder bodyStr = new StringBuilder(); StringBuilder bodyStr = new StringBuilder();

View File

@ -27,6 +27,7 @@ import io.metersphere.service.FileService;
import io.metersphere.service.ScheduleService; import io.metersphere.service.ScheduleService;
import io.metersphere.track.service.TestCaseService; import io.metersphere.track.service.TestCaseService;
import org.apache.dubbo.common.URL; import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.constants.CommonConstants;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@ -264,7 +265,7 @@ public class APITestService {
ApiImportParser apiImportParser = ApiImportParserFactory.getApiImportParser(request.getPlatform()); ApiImportParser apiImportParser = ApiImportParserFactory.getApiImportParser(request.getPlatform());
ApiImport apiImport = null; ApiImport apiImport = null;
try { try {
apiImport = apiImportParser.parse(file == null ? null : file.getInputStream(), request); apiImport = Objects.requireNonNull(apiImportParser).parse(file.getInputStream(), request);
} catch (Exception e) { } catch (Exception e) {
LogUtil.error(e.getMessage(), e); LogUtil.error(e.getMessage(), e);
MSException.throwException(Translator.get("parse_data_error")); MSException.throwException(Translator.get("parse_data_error"));
@ -291,21 +292,26 @@ public class APITestService {
} }
public List<DubboProvider> getProviders(RegistryCenter registry) { public List<DubboProvider> getProviders(RegistryCenter registry) {
ProviderService providerService = ProviderService.get("provider"); ProviderService providerService = ProviderService.get(registry.getAddress());
List<String> providers = providerService.getProviders(registry.getProtocol(), registry.getAddress(), registry.getGroup()); List<String> providers = providerService.getProviders(registry.getProtocol(), registry.getAddress(), registry.getGroup());
List<DubboProvider> providerList = new ArrayList<>(); List<DubboProvider> list = new ArrayList<>();
providers.forEach(p -> { providers.forEach(p -> {
Map<String, URL> services = providerService.findByService(p);
services.forEach((k, v) -> {
DubboProvider provider = new DubboProvider(); DubboProvider provider = new DubboProvider();
provider.setVersion(v.getParameter("version")); String[] info = p.split(":");
provider.setService(v.getServiceKey()); if (info.length > 1) {
provider.setServiceInterface(v.getServiceInterface()); provider.setVersion(info[1]);
String[] methods = v.getParameter("methods").split(","); }
provider.setService(info[0]);
provider.setServiceInterface(p);
Map<String, URL> services = providerService.findByService(p);
if (services != null && !services.isEmpty()) {
String[] methods = services.values().stream().findFirst().get().getParameter(CommonConstants.METHODS_KEY).split(",");
provider.setMethods(Arrays.asList(methods)); provider.setMethods(Arrays.asList(methods));
providerList.add(provider); } else {
provider.setMethods(new ArrayList<>());
}
list.add(provider);
}); });
}); return list;
return providerList;
} }
} }

View File

@ -15,12 +15,11 @@ import io.metersphere.performance.controller.request.ReportRequest;
import io.metersphere.performance.service.ReportService; import io.metersphere.performance.service.ReportService;
import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.util.List; import java.util.List;
@RestController @RestController
@ -112,11 +111,15 @@ public class PerformanceReportController {
} }
@GetMapping("log/download/{reportId}/{resourceId}") @GetMapping("log/download/{reportId}/{resourceId}")
public ResponseEntity<byte[]> downloadLog(@PathVariable String reportId, @PathVariable String resourceId) { public void downloadLog(@PathVariable String reportId, @PathVariable String resourceId, HttpServletResponse response) throws Exception {
byte[] bytes = reportService.downloadLog(reportId, resourceId); try (OutputStream outputStream = response.getOutputStream()) {
return ResponseEntity.ok() List<String> content = reportService.downloadLog(reportId, resourceId);
.contentType(MediaType.parseMediaType("application/octet-stream")) response.setContentType("application/x-download");
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"jmeter.log\"") response.addHeader("Content-Disposition", "attachment;filename=jmeter.log");
.body(bytes); for (String log : content) {
outputStream.write(log.getBytes());
}
outputStream.flush();
}
} }
} }

View File

@ -25,6 +25,7 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
@Service @Service
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@ -210,13 +211,13 @@ public class ReportService {
return loadTestReportLogMapper.selectByExampleWithBLOBs(example); return loadTestReportLogMapper.selectByExampleWithBLOBs(example);
} }
public byte[] downloadLog(String reportId, String resourceId) { public List<String> downloadLog(String reportId, String resourceId) {
LoadTestReportLogExample example = new LoadTestReportLogExample(); LoadTestReportLogExample example = new LoadTestReportLogExample();
example.createCriteria().andReportIdEqualTo(reportId).andResourceIdEqualTo(resourceId); example.createCriteria().andReportIdEqualTo(reportId).andResourceIdEqualTo(resourceId);
example.setOrderByClause("part desc");
List<LoadTestReportLog> loadTestReportLogs = loadTestReportLogMapper.selectByExampleWithBLOBs(example); List<LoadTestReportLog> loadTestReportLogs = loadTestReportLogMapper.selectByExampleWithBLOBs(example);
String content = loadTestReportLogs.stream().map(LoadTestReportLog::getContent).reduce("", (a, b) -> a + b); return loadTestReportLogs.stream().map(LoadTestReportLog::getContent).collect(Collectors.toList());
return content.getBytes();
} }
public LoadTestReport getReport(String reportId) { public LoadTestReport getReport(String reportId) {

View File

@ -7,7 +7,7 @@
<parent> <parent>
<artifactId>metersphere-server</artifactId> <artifactId>metersphere-server</artifactId>
<groupId>io.metersphere</groupId> <groupId>io.metersphere</groupId>
<version>1.0</version> <version>1.1</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@ -82,7 +82,7 @@
} }
.metric .code { .metric .code {
width: 120px; min-width: 120px;
} }
.metric .code .value { .metric .code .value {
@ -98,4 +98,9 @@
border-left: 1px solid #EBEEF5; border-left: 1px solid #EBEEF5;
margin-right: 20px; margin-right: 20px;
} }
.metric .message {
max-height: 114px;
overflow-y: auto;
}
</style> </style>

View File

@ -96,6 +96,7 @@
this.methods = this.methodMap[this.request.interface].methods; this.methods = this.methodMap[this.request.interface].methods;
} }
this.loading = false; this.loading = false;
this.$success(this.$t('api_test.request.dubbo.get_provider_success'));
}).catch(() => { }).catch(() => {
this.loading = false; this.loading = false;
this.$warning(this.$t('api_test.request.dubbo.check_registry_center')); this.$warning(this.$t('api_test.request.dubbo.check_registry_center'));

View File

@ -194,6 +194,18 @@ class DubboConfig extends BaseConfig {
super(); super();
this.configCenter = new ConfigCenter(options.configCenter) this.configCenter = new ConfigCenter(options.configCenter)
this.registryCenter = new RegistryCenter(options.registryCenter) this.registryCenter = new RegistryCenter(options.registryCenter)
if (options.consumerAndService === undefined) {
options.consumerAndService = {
timeout: undefined,
version: undefined,
retries: undefined,
cluster: undefined,
group: undefined,
connections: undefined,
async: undefined,
loadBalance: undefined
}
}
this.consumerAndService = new ConsumerAndService(options.consumerAndService) this.consumerAndService = new ConsumerAndService(options.consumerAndService)
} }
} }
@ -259,7 +271,7 @@ export class HttpRequest extends Request {
} }
isValid(environmentId) { isValid(environmentId) {
if (this.useEnvironment){ if (this.useEnvironment) {
if (!environmentId) { if (!environmentId) {
return { return {
isValid: false, isValid: false,

View File

@ -417,6 +417,7 @@ export default {
input_interface: "Please enter the interface", input_interface: "Please enter the interface",
input_method: "Please enter the method", input_method: "Please enter the method",
input_config_center: "Please enter the config center", input_config_center: "Please enter the config center",
get_provider_success: "get provider list to finish",
input_registry_center: "Please enter the registry center", input_registry_center: "Please enter the registry center",
input_consumer_service: "Please enter the consumer & service", input_consumer_service: "Please enter the consumer & service",
check_registry_center: "Can't get interface list, please check the registry center", check_registry_center: "Can't get interface list, please check the registry center",

View File

@ -418,6 +418,7 @@ export default {
input_config_center: "请输入Config Center", input_config_center: "请输入Config Center",
input_registry_center: "请输入Registry Center", input_registry_center: "请输入Registry Center",
input_consumer_service: "请输入Consumer & Service", input_consumer_service: "请输入Consumer & Service",
get_provider_success: "获取成功",
check_registry_center: "获取失败请检查Registry Center", check_registry_center: "获取失败请检查Registry Center",
form_description: "如果当前配置项无值,则取场景配置项的值", form_description: "如果当前配置项无值,则取场景配置项的值",
} }

View File

@ -416,6 +416,7 @@ export default {
input_interface: "請輸入Interface", input_interface: "請輸入Interface",
input_method: "請輸入Method", input_method: "請輸入Method",
input_config_center: "請輸入Config Center", input_config_center: "請輸入Config Center",
get_provider_success: "獲取成功",
input_registry_center: "請輸入Registry Center", input_registry_center: "請輸入Registry Center",
input_consumer_service: "請輸入Consumer & Service", input_consumer_service: "請輸入Consumer & Service",
check_registry_center: "獲取失敗請檢查Registry Center", check_registry_center: "獲取失敗請檢查Registry Center",

View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>io.metersphere</groupId> <groupId>io.metersphere</groupId>
<artifactId>metersphere-server</artifactId> <artifactId>metersphere-server</artifactId>
<version>1.0</version> <version>1.1</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<parent> <parent>