diff --git a/.github/workflows/plugins-test.yaml b/.github/workflows/plugins-test.yaml index 28f3929cdb6de4b3f5c4dfd8c45c9ee273b511cd..3fc8df48a84a9c3dd5acd1b205e01af4bb956027 100644 --- a/.github/workflows/plugins-test.yaml +++ b/.github/workflows/plugins-test.yaml @@ -464,4 +464,30 @@ jobs: - name: Build the Docker image run: ./mvnw -f test/plugin/pom.xml clean package -DskipTests docker:build -DBUILD_NO=local >/dev/null - name: Run kafka 0.11.0.0-2.3.0 (16) - run: bash test/plugin/run.sh kafka-scenario \ No newline at end of file + run: bash test/plugin/run.sh kafka-scenario + + MySQL: + runs-on: ubuntu-18.04 + timeout-minutes: 90 + strategy: + fail-fast: true + steps: + - uses: actions/checkout@v1 + with: + submodules: true + - uses: actions/cache@v1 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - uses: actions/setup-java@v1 + with: + java-version: 8 + - name: Build SkyWalking Agent + run: ./mvnw clean package -DskipTests -Pagent >/dev/null + - name: Build the Docker image + run: ./mvnw -f test/plugin/pom.xml clean package -DskipTests docker:build -DBUILD_NO=local >/dev/null + - name: Run mysql 5.1.2-8.0.15 (53) + run: bash test/plugin/run.sh mysql-scenario + diff --git a/test/plugin/scenarios/mysql-scenario/bin/startup.sh b/test/plugin/scenarios/mysql-scenario/bin/startup.sh new file mode 100644 index 0000000000000000000000000000000000000000..c26c6003027255f013a9af7c0812ac4e6737a8ef --- /dev/null +++ b/test/plugin/scenarios/mysql-scenario/bin/startup.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# 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. + +home="$(cd "$(dirname $0)"; pwd)" + +java -jar ${agent_opts} ${home}/../libs/mysql-scenario.jar & \ No newline at end of file diff --git a/test/plugin/scenarios/mysql-scenario/config/expectedData.yaml b/test/plugin/scenarios/mysql-scenario/config/expectedData.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9b571d7cb02651a961a7051fdb4940ff0e35adfb --- /dev/null +++ b/test/plugin/scenarios/mysql-scenario/config/expectedData.yaml @@ -0,0 +1,118 @@ +# 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. +registryItems: + applications: + - {mysql-scenario: nq 0} + instances: + - {mysql-scenario: 1} + operationNames: + - mysql-scenario: [Mysql/JDBI/Statement/execute, /mysql-scenario/case/healthCheck, + Mysql/JDBI/PreparedStatement/execute, /mysql-scenario/case/mysql-scenario, Mysql/JDBI/Connection/close] +segmentItems: + - applicationCode: mysql-scenario + segmentSize: ge 2 + segments: + - segmentId: not null + spans: + - operationName: Mysql/JDBI/PreparedStatement/execute + operationId: eq 0 + parentSpanId: 0 + spanId: 1 + tags: + - {key: "db.type", value: "sql"} + - {key: "db.instance", value: "test"} + - {key: "db.statement", value: "CREATE TABLE test_007(\nid VARCHAR(1) PRIMARY KEY, \nvalue VARCHAR(1) NOT NULL)"} + logs: [] + startTime: nq 0 + endTime: nq 0 + isError: false + spanLayer: Database + spanType: Exit + componentName: null + componentId: 33 + peer: mysql-server:3306 + peerId: eq 0 + - operationName: Mysql/JDBI/PreparedStatement/execute + operationId: eq 0 + parentSpanId: 0 + spanId: 2 + tags: + - {key: "db.type", value: "sql"} + - {key: "db.instance", value: "test"} + - {key: "db.statement", value: "INSERT INTO test_007(id, value) VALUES(?,?)"} + logs: [] + startTime: nq 0 + endTime: nq 0 + isError: false + spanLayer: Database + spanType: Exit + componentName: null + componentId: 33 + peer: mysql-server:3306 + peerId: eq 0 + - operationName: Mysql/JDBI/Statement/execute + operationId: eq 0 + parentSpanId: 0 + spanId: 3 + tags: + - {key: "db.type", value: "sql"} + - {key: "db.instance", value: "test"} + - {key: "db.statement", value: "DROP table test_007"} + logs: [] + startTime: nq 0 + endTime: nq 0 + isError: false + spanLayer: Database + spanType: Exit + componentName: null + componentId: 33 + peer: mysql-server:3306 + peerId: eq 0 + - operationName: Mysql/JDBI/Connection/close + operationId: eq 0 + parentSpanId: 0 + spanId: 4 + tags: + - {key: "db.type", value: "sql"} + - {key: "db.instance", value: "test"} + - {key: "db.statement", value: ""} + logs: [] + startTime: nq 0 + endTime: nq 0 + isError: false + spanLayer: Database + spanType: Exit + componentName: null + componentId: 33 + peer: mysql-server:3306 + peerId: eq 0 + - operationName: /mysql-scenario/case/mysql-scenario + operationId: eq 0 + parentSpanId: -1 + spanId: 0 + startTime: nq 0 + endTime: nq 0 + spanLayer: Http + isError: false + spanType: Entry + componentName: null + componentId: 1 + tags: + - {key: url, value: 'http://localhost:8080/mysql-scenario/case/mysql-scenario'} + - {key: http.method, value: GET} + logs: [] + peer: null + peerId: eq 0 diff --git a/test/plugin/scenarios/mysql-scenario/configuration.yml b/test/plugin/scenarios/mysql-scenario/configuration.yml new file mode 100644 index 0000000000000000000000000000000000000000..ed4bf0521094f9bdfceeb0784bd39f355261a582 --- /dev/null +++ b/test/plugin/scenarios/mysql-scenario/configuration.yml @@ -0,0 +1,33 @@ +# 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. + +type: jvm +entryService: http://localhost:8080/mysql-scenario/case/mysql-scenario +healthCheck: http://localhost:8080/mysql-scenario/case/healthCheck +startScript: ./bin/startup.sh +framework: mysql-scenario +environment: +depends_on: + - mysql-server +dependencies: + mysql-server: + image: mysql:5.7 + hostname: mysql-server + expose: + - "3306" + environment: + - MYSQL_ROOT_PASSWORD=root + - MYSQL_DATABASE=test \ No newline at end of file diff --git a/test/plugin/scenarios/mysql-scenario/pom.xml b/test/plugin/scenarios/mysql-scenario/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..21d14480eb0468da31eef040466e88109899898c --- /dev/null +++ b/test/plugin/scenarios/mysql-scenario/pom.xml @@ -0,0 +1,120 @@ + + + + + org.apache.skywalking.apm.testcase + mysql-scenario + 1.0.0 + jar + + 4.0.0 + + + UTF-8 + 1.8 + + 5.1.5 + ${test.framework.version} + + 2.1.6.RELEASE + + + skywalking-mysql-scenario + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot-version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + + mysql + mysql-connector-java + ${test.framework.version} + + + + + mysql-scenario + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + maven-compiler-plugin + + ${compiler.version} + ${compiler.version} + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-assembly-plugin + + + assemble + package + + single + + + + src/main/assembly/assembly.xml + + ./target/ + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/mysql-scenario/src/main/assembly/assembly.xml b/test/plugin/scenarios/mysql-scenario/src/main/assembly/assembly.xml new file mode 100644 index 0000000000000000000000000000000000000000..39d1eb34095da07aff2f9381045e2e2dbd911ba7 --- /dev/null +++ b/test/plugin/scenarios/mysql-scenario/src/main/assembly/assembly.xml @@ -0,0 +1,41 @@ + + + + + zip + + + + + ./bin + 0775 + + + + + + ${project.build.directory}/mysql-scenario.jar + ./libs + 0775 + + + diff --git a/test/plugin/scenarios/mysql-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/Application.java b/test/plugin/scenarios/mysql-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/Application.java new file mode 100644 index 0000000000000000000000000000000000000000..4bceb3a0aa23ce9659e3a53e8a85ab9e2aebedc7 --- /dev/null +++ b/test/plugin/scenarios/mysql-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/Application.java @@ -0,0 +1,35 @@ +/* + * 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.skywalking.apm.testcase.mysql; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + try { + SpringApplication.run(Application.class, args); + } catch (Exception e) { + // Never do this + } + } +} diff --git a/test/plugin/scenarios/mysql-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/MysqlConfig.java b/test/plugin/scenarios/mysql-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/MysqlConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..6e9103c914d3633c246eaa924570e1c8cbdc3945 --- /dev/null +++ b/test/plugin/scenarios/mysql-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/MysqlConfig.java @@ -0,0 +1,58 @@ +/* + * 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.skywalking.apm.testcase.mysql; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class MysqlConfig { + private static Logger logger = LogManager.getLogger(MysqlConfig.class); + private static String url; + private static String userName; + private static String password; + + static { + InputStream inputStream = MysqlConfig.class.getClassLoader().getResourceAsStream("/jdbc.properties"); + Properties properties = new Properties(); + try { + properties.load(inputStream); + } catch (IOException e) { + logger.error("Failed to load config", e); + } + + url = properties.getProperty("mysql.url"); + userName = properties.getProperty("mysql.username"); + password = properties.getProperty("mysql.password"); + } + + public static String getUrl() { + return url; + } + + public static String getUserName() { + return userName; + } + + public static String getPassword() { + return password; + } +} diff --git a/test/plugin/scenarios/mysql-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/SQLExecutor.java b/test/plugin/scenarios/mysql-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/SQLExecutor.java new file mode 100644 index 0000000000000000000000000000000000000000..405189907679fc0a15bfbac271ed15f03bf6a604 --- /dev/null +++ b/test/plugin/scenarios/mysql-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/SQLExecutor.java @@ -0,0 +1,64 @@ +/* + * 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.skywalking.apm.testcase.mysql; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; + +public class SQLExecutor { + private Connection connection; + + public SQLExecutor() throws SQLException { + try { + Class.forName("com.mysql.jdbc.Driver"); + } catch (ClassNotFoundException e) { + // + } + connection = DriverManager.getConnection(MysqlConfig.getUrl(), MysqlConfig.getUserName(), MysqlConfig.getPassword()); + } + + public void createTable(String sql) throws SQLException { + PreparedStatement preparedStatement = connection.prepareStatement(sql); + preparedStatement.execute(); + preparedStatement.close(); + } + + public void insertData(String sql, String id, String value) throws SQLException { + PreparedStatement preparedStatement = connection.prepareStatement(sql); + preparedStatement.setString(1, id); + preparedStatement.setString(2, value); + preparedStatement.execute(); + preparedStatement.close(); + } + + public void dropTable(String sql) throws SQLException { + Statement preparedStatement = connection.createStatement(); + preparedStatement.execute(sql); + preparedStatement.close(); + } + + public void closeConnection() throws SQLException { + if (this.connection != null) { + this.connection.close(); + } + } +} diff --git a/test/plugin/scenarios/mysql-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/controller/CaseController.java b/test/plugin/scenarios/mysql-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/controller/CaseController.java new file mode 100644 index 0000000000000000000000000000000000000000..d1f5e4697fb4c470e2595b360e6173f742514a2f --- /dev/null +++ b/test/plugin/scenarios/mysql-scenario/src/main/java/org/apache/skywalking/apm/testcase/mysql/controller/CaseController.java @@ -0,0 +1,76 @@ +/* + * 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.skywalking.apm.testcase.mysql.controller; + +import java.sql.SQLException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.skywalking.apm.testcase.mysql.SQLExecutor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/case") +public class CaseController { + + private static final Logger logger = LogManager.getLogger(CaseController.class); + + private static final String SUCCESS = "Success"; + + private static final String CREATE_TABLE_SQL = "CREATE TABLE test_007(\n" + + "id VARCHAR(1) PRIMARY KEY, \n" + + "value VARCHAR(1) NOT NULL)"; + private static final String INSERT_DATA_SQL = "INSERT INTO test_007(id, value) VALUES(?,?)"; + private static final String QUERY_DATA_SQL = "SELECT id, value FROM test_007 WHERE id=?"; + private static final String DELETE_DATA_SQL = "DELETE FROM test_007 WHERE id=?"; + private static final String DROP_TABLE_SQL = "DROP table test_007"; + + @RequestMapping("/mysql-scenario") + @ResponseBody + public String testcase() { + SQLExecutor sqlExecute = null; + try { + sqlExecute = new SQLExecutor(); + sqlExecute.createTable(CREATE_TABLE_SQL); + sqlExecute.insertData(INSERT_DATA_SQL, "1", "1"); + sqlExecute.dropTable(DROP_TABLE_SQL); + } catch (SQLException e) { + logger.error("Failed to execute sql.", e); + } finally { + if (sqlExecute != null) { + try { + sqlExecute.closeConnection(); + } catch (SQLException e) { + logger.error("Failed to close connection.", e); + } + } + } + + return SUCCESS; + } + + @RequestMapping("/healthCheck") + @ResponseBody + public String healthCheck() { + // your codes + return SUCCESS; + } + +} diff --git a/test/plugin/scenarios/mysql-scenario/src/main/resources/application.yaml b/test/plugin/scenarios/mysql-scenario/src/main/resources/application.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8dc5e28d15441ab7c0b9279b69cd8582eb9c2e6b --- /dev/null +++ b/test/plugin/scenarios/mysql-scenario/src/main/resources/application.yaml @@ -0,0 +1,23 @@ +# +# 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. +# +# +server: + port: 8080 + servlet: + context-path: /mysql-scenario +logging: + config: classpath:log4j2.xml \ No newline at end of file diff --git a/test/plugin/scenarios/mysql-scenario/src/main/resources/jdbc.properties b/test/plugin/scenarios/mysql-scenario/src/main/resources/jdbc.properties new file mode 100644 index 0000000000000000000000000000000000000000..7d458082538de32bb0c016be04c4d03864f7f241 --- /dev/null +++ b/test/plugin/scenarios/mysql-scenario/src/main/resources/jdbc.properties @@ -0,0 +1,19 @@ +# 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 +# "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. + +mysql.url=jdbc:mysql://mysql-server:3306/test?serverTimezone=CST +mysql.username=root +mysql.password=root diff --git a/test/plugin/scenarios/mysql-scenario/src/main/resources/log4j2.xml b/test/plugin/scenarios/mysql-scenario/src/main/resources/log4j2.xml new file mode 100644 index 0000000000000000000000000000000000000000..9849ed5a8abd116a9000e64cc18f05e583f21c98 --- /dev/null +++ b/test/plugin/scenarios/mysql-scenario/src/main/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plugin/scenarios/mysql-scenario/support-version.list b/test/plugin/scenarios/mysql-scenario/support-version.list new file mode 100644 index 0000000000000000000000000000000000000000..b099c65dbf0f49523510f5829626d7f58335d32f --- /dev/null +++ b/test/plugin/scenarios/mysql-scenario/support-version.list @@ -0,0 +1,71 @@ +# 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 +# "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. + +# lists your version here + +8.0.15 +8.0.14 +8.0.13 +8.0.12 +8.0.11 +6.0.6 +6.0.5 +6.0.4 +6.0.3 +6.0.2 +5.1.45 +5.1.44 +5.1.43 +5.1.42 +5.1.41 +5.1.40 +5.1.39 +5.1.38 +5.1.37 +5.1.36 +5.1.35 +5.1.34 +5.1.33 +5.1.32 +5.1.31 +5.1.30 +5.1.29 +5.1.28 +5.1.27 +5.1.26 +5.1.25 +5.1.24 +5.1.23 +5.1.22 +5.1.21 +5.1.20 +5.1.19 +5.1.18 +5.1.17 +5.1.16 +5.1.15 +5.1.14 +5.1.13 +5.1.12 +5.1.11 +5.1.10 +5.1.9 +5.1.8 +5.1.6 +5.1.5 +5.1.4 +5.1.3 +5.1.2 \ No newline at end of file