├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.gradle ├── client ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── github │ │ └── e13mort │ │ └── stf │ │ └── console │ │ ├── AdbRunner.java │ │ ├── App.java │ │ ├── ErrorHandler.java │ │ ├── SimpleConsoleFormatter.java │ │ ├── StfCommander.java │ │ ├── StfCommanderContext.java │ │ └── commands │ │ ├── CommandContainer.java │ │ ├── ConsoleDeviceParamsImpl.java │ │ ├── DisconnectCommand.java │ │ ├── EmptyDevicesException.java │ │ ├── FilterDescriptionConverterImpl.java │ │ ├── HelpCommand.java │ │ ├── HelpCommandCreator.java │ │ ├── UnknownCommandException.java │ │ ├── cache │ │ ├── DeviceListCache.java │ │ └── DeviceListCacheImpl.java │ │ ├── connect │ │ ├── CacheDevicesConnector.java │ │ ├── ConnectCommand.java │ │ ├── DeviceConnector.java │ │ ├── FileParamsDeviceConnector.java │ │ ├── InvalidCacheIndexException.java │ │ ├── MyDevicesConnector.java │ │ └── ParamsConnector.java │ │ └── devices │ │ ├── DeviceMapper.java │ │ ├── DevicesCommand.java │ │ ├── DocumentsLoader.java │ │ ├── PredefinedDeviceReader.java │ │ ├── ReflectionDeviceReader.java │ │ └── TablePrinter.java │ └── test │ └── java │ └── com │ └── github │ └── e13mort │ └── stf │ └── console │ ├── BaseStfCommanderTest.java │ ├── StfCommanderTest.java │ └── commands │ └── connect │ └── ConnectCommandTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── junit.gradle ├── settings.gradle └── stf_usage.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.war 8 | *.ear 9 | 10 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 11 | hs_err_pid* 12 | 13 | .idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | # cache between builds 7 | cache: 8 | directories: 9 | - $HOME/.m2 10 | - $HOME/.gradle -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # STF console client 2 | A console client for the [Smartphone Test Farm](https://github.com/openstf/stf) service 3 | 4 | [![Build Status](https://travis-ci.org/e13mort/stf-console-client.svg?branch=master)](https://travis-ci.org/e13mort/stf-console-client) 5 | ## Download 6 | [Latest version](https://github.com/e13mort/stf-console-client/releases/latest) 7 | 8 | [All versions](https://github.com/e13mort/stf-console-client/releases/) 9 | 10 | ![stf devices](stf_usage.png) 11 | 12 | ## Configuration 13 | 14 | * Download and extract an artifact from "releases" page into some target directory 15 | * Create file "farm.properties" either in your home directory or in the target app directory 16 | * Add following properties into the file: 17 | 18 | stf.url=/api/v1/ 19 | stf.key= 20 | stf.timeout= 21 | android_sdk= 22 | * Optionally, add the app's directory into your PATH environment var 23 | 24 | ## Usage 25 | stf [command] [command options] 26 | 27 | Commands: 28 | 29 | disconnect Disconnect from all of currently connected devices 30 | Usage: disconnect [options] 31 | Options: 32 | -s 33 | Disconnect from the specified devices 34 | Default: [] 35 | 36 | 37 | devices Print list of available devices 38 | Usage: devices [options] 39 | Options: 40 | --all 41 | Show all devices. By default only available devices are returned. 42 | Default: false 43 | --my-columns 44 | Use columns from web panel 45 | Default: false 46 | -abi 47 | Filter by device abi architecture 48 | -api 49 | Filter by device api level 50 | Default: 0 51 | -count 52 | Filter devices by count 53 | Default: 0 54 | -maxApi 55 | Filter by device max api level 56 | Default: 0 57 | -minApi 58 | Filter by device min api level 59 | Default: 0 60 | -name 61 | Filter devices by its name 62 | -provider 63 | Filter devices by provider 64 | -serial 65 | Filter devices by serial number 66 | 67 | connect Connect to devices 68 | Usage: connect [options] 69 | Options: 70 | --all 71 | Show all devices. By default only available devices are returned. 72 | Default: false 73 | --my 74 | Connect to currently taken devices 75 | Default: false 76 | -abi 77 | Filter by device abi architecture 78 | -api 79 | Filter by device api level 80 | Default: 0 81 | -count 82 | Filter devices by count 83 | Default: 0 84 | -f 85 | Read connection params from a file 86 | -l 87 | Connect to devices by its indexes from the results of previous 88 | "devices" command. E.g. "-l 1 2 5" 89 | Default: [] 90 | -maxApi 91 | Filter by device max api level 92 | Default: 0 93 | -minApi 94 | Filter by device min api level 95 | Default: 0 96 | -name 97 | Filter devices by its name 98 | -provider 99 | Filter devices by provider 100 | -serial 101 | Filter devices by serial number 102 | -u 103 | Read connection params from an url 104 | 105 | 106 | #### Store connection parameters in a file 107 | Connection parameters might be stored as a separate file: 108 | 109 | confing.json: 110 | { 111 | "count": 2, 112 | "api": 23, 113 | "names": "nexus5,nexus6", 114 | "minApi": 21, 115 | "providers": "~support,autotests", 116 | "serials": "3a4674bce45644", 117 | "maxApi": 26, 118 | "abi": "arm" 119 | } 120 | 121 | usage: stf connect -f config.json 122 | stf connect -u http://sample.com/params.json 123 | 124 | All fields are optional. For the "providers", "serials" and "names" fields the `~` sign might be used to inverse filter. 125 | E.g config with parameter 126 | 127 | "providers": "~support,autotests" 128 | 129 | means 'use all available devices except the ones which belongs to the "support" and "autotests" groups'. -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | maven { 7 | url "https://plugins.gradle.org/m2/" 8 | } 9 | dependencies { 10 | classpath "gradle.plugin.nl.javadude.gradle.plugins:license-gradle-plugin:0.13.1" 11 | } 12 | } 13 | 14 | project.ext { 15 | githubKey = project.getProperties().get('github_key', 'unknown_key') 16 | } 17 | } 18 | 19 | allprojects { 20 | repositories { 21 | jcenter() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /client/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'maven-publish' 3 | apply plugin: 'application' 4 | apply plugin: 'distribution' 5 | apply from: '../junit.gradle' 6 | apply plugin: 'co.riiid.gradle' 7 | apply plugin: 'me.tatarka.retrolambda' 8 | 9 | sourceCompatibility = JavaVersion.VERSION_1_8 10 | targetCompatibility = JavaVersion.VERSION_1_8 11 | 12 | def ARTIFACT_ID = 'stf-console-client' 13 | def ARTIFACT_VERSION = '0.3.4' 14 | def SCRIPT_NAME = 'stf' 15 | 16 | buildscript { 17 | repositories { 18 | jcenter() 19 | } 20 | dependencies { 21 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' 22 | classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.0-RC3' 23 | classpath 'co.riiid:gradle-github-plugin:0.4.2' 24 | classpath 'me.tatarka:gradle-retrolambda:3.7.0' 25 | } 26 | } 27 | 28 | repositories { 29 | mavenLocal() 30 | mavenCentral() 31 | jcenter() 32 | maven { url 'http://dl.bintray.com/e13mort/maven' } 33 | } 34 | 35 | dependencies { 36 | compile 'com.github.e13mort:open-stf-client:0.3.4' 37 | 38 | compile 'io.reactivex.rxjava2:rxjava:2.0.7' 39 | compile 'com.squareup.retrofit2:retrofit:2.1.0' 40 | compile "com.beust:jcommander:1.71" 41 | compile 'de.vandermeer:asciitable-j7:1.0.1' 42 | 43 | testCompile "org.mockito:mockito-core:2.+" 44 | } 45 | 46 | mainClassName = "com.github.e13mort.stf.console.App" 47 | 48 | version ARTIFACT_VERSION 49 | 50 | distributions { 51 | main { 52 | baseName ARTIFACT_ID 53 | } 54 | } 55 | 56 | startScripts { 57 | applicationName SCRIPT_NAME 58 | } 59 | 60 | publishing { 61 | publications { 62 | mavenJava(MavenPublication) { 63 | from components.java 64 | 65 | groupId GROUP_ID 66 | artifactId ARTIFACT_ID 67 | version ARTIFACT_VERSION 68 | } 69 | } 70 | } 71 | 72 | github { 73 | owner = 'e13mort' 74 | repo = ARTIFACT_ID 75 | token = rootProject.ext.githubKey 76 | tagName = "${ARTIFACT_VERSION}" 77 | assets = [ 78 | "client/build/distributions/stf-console-client-${ARTIFACT_VERSION}.tar", 79 | "client/build/distributions/stf-console-client-${ARTIFACT_VERSION}.zip" 80 | ] 81 | } 82 | 83 | retrolambda { 84 | javaVersion JavaVersion.VERSION_1_7 85 | defaultMethods true 86 | incremental true 87 | } -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/AdbRunner.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console; 2 | 3 | import java.io.*; 4 | import java.util.logging.Level; 5 | import java.util.logging.Logger; 6 | 7 | public class AdbRunner { 8 | private static final String ADB_DIRECTORY = "platform-tools"; 9 | private final String androidSdkPath; 10 | private static final String TAG_ADB = "ADB"; 11 | private final Logger logger; 12 | 13 | AdbRunner(String androidSdkPath, Logger logger) { 14 | if (androidSdkPath == null) { 15 | androidSdkPath = ""; 16 | } 17 | this.androidSdkPath = androidSdkPath; 18 | this.logger = logger; 19 | } 20 | 21 | public void connectToDevice(String connectionUrl) throws IOException { 22 | runComplexCommand("adb", "connect", connectionUrl); 23 | runComplexCommand("adb", "wait-for-device"); 24 | } 25 | 26 | private void runComplexCommand(String... params) throws IOException { 27 | File adb = new File(androidSdkPath + File.separator + ADB_DIRECTORY); 28 | Process exec = new ProcessBuilder(params) 29 | .directory(adb) 30 | .start(); 31 | runProcess(exec); 32 | } 33 | 34 | private void runProcess(Process process) throws IOException { 35 | final InputStream stream = process.getInputStream(); 36 | final BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); 37 | try { 38 | String line; 39 | while ((line = reader.readLine()) != null) { 40 | log(TAG_ADB, line); 41 | } 42 | } catch (IOException e) { 43 | log(e); 44 | } finally { 45 | reader.close(); 46 | stream.close(); 47 | } 48 | } 49 | 50 | private void log(Exception e) { 51 | logger.log(Level.INFO, "message {0}", e.getMessage()); 52 | } 53 | 54 | private void log(String tag, String message) { 55 | logger.info(tag + ": " + message); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/App.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console; 2 | 3 | import com.github.e13mort.stf.console.commands.EmptyDevicesException; 4 | import com.github.e13mort.stf.console.commands.HelpCommandCreator.HelpCommandCreatorImpl; 5 | import com.github.e13mort.stf.console.commands.UnknownCommandException; 6 | 7 | import java.io.IOException; 8 | import java.util.logging.*; 9 | 10 | public class App { 11 | 12 | private static ErrorHandler errorHandler = throwable -> {}; 13 | 14 | public static void main(String... args) throws IOException { 15 | final Logger logger = createLogger(); 16 | errorHandler = new StfErrorHandler(logger); 17 | StfCommanderContext.create(logger) 18 | .map(context -> StfCommander.create(context, new HelpCommandCreatorImpl(), errorHandler, args)) 19 | .subscribe(StfCommander::execute, errorHandler::handle); 20 | } 21 | 22 | static class StfErrorHandler implements ErrorHandler { 23 | 24 | private final Logger logger; 25 | 26 | StfErrorHandler(Logger logger) { 27 | this.logger = logger; 28 | } 29 | 30 | @Override 31 | public void handle(Throwable throwable) { 32 | if (throwable instanceof EmptyDevicesException) { 33 | logger.log(Level.INFO ,"There's no devices"); 34 | } else if (throwable instanceof UnknownCommandException) { 35 | logger.log(Level.INFO,"Unknown command: " + throwable); 36 | } else if (throwable != null) { 37 | logger.log(Level.INFO,"Error: ", throwable); 38 | } 39 | } 40 | 41 | } 42 | 43 | private static Logger createLogger() { 44 | final Logger logger = Logger.getLogger(""); 45 | 46 | for (Handler h: logger.getHandlers()) { 47 | logger.removeHandler(h); 48 | } 49 | 50 | final ConsoleHandler handler = new ConsoleHandler(); 51 | handler.setFormatter(new SimpleConsoleFormatter()); 52 | logger.addHandler(handler); 53 | return logger; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/ErrorHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console; 2 | 3 | public interface ErrorHandler { 4 | ErrorHandler EMPTY = error -> {}; 5 | 6 | void handle(Throwable throwable); 7 | } 8 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/SimpleConsoleFormatter.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console; 2 | 3 | import java.io.PrintWriter; 4 | import java.io.StringWriter; 5 | import java.util.logging.Formatter; 6 | import java.util.logging.Level; 7 | import java.util.logging.LogRecord; 8 | 9 | class SimpleConsoleFormatter extends Formatter { 10 | @Override 11 | public String format(LogRecord record) { 12 | final String message = formatMessage(record); 13 | final String throwable = formatThrowable(record); 14 | return String.format("%s %s \n", message, throwable); 15 | } 16 | 17 | private String formatThrowable(LogRecord record) { 18 | String throwable = ""; 19 | if (record.getThrown() != null) { 20 | if (record.getLevel() == Level.WARNING) { 21 | StringWriter sw = new StringWriter(); 22 | PrintWriter pw = new PrintWriter(sw); 23 | pw.println(); 24 | record.getThrown().printStackTrace(pw); 25 | pw.close(); 26 | throwable = sw.toString(); 27 | } else { 28 | throwable = record.getThrown().getMessage(); 29 | } 30 | } 31 | return throwable; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/StfCommander.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console; 2 | 3 | import com.beust.jcommander.JCommander; 4 | import com.github.e13mort.stf.console.commands.CommandContainer; 5 | import com.github.e13mort.stf.console.commands.HelpCommandCreator; 6 | import com.github.e13mort.stf.console.commands.UnknownCommandException; 7 | import io.reactivex.Completable; 8 | 9 | public class StfCommander { 10 | private final CommandContainer commandContainer; 11 | private final CommandContainer.Command defaultCommand; 12 | private final ErrorHandler errorHandler; 13 | private String commandName; 14 | 15 | private StfCommander( 16 | String commandName, 17 | CommandContainer commandContainer, 18 | CommandContainer.Command defaultCommand, 19 | ErrorHandler errorHandler) { 20 | this.commandName = commandName; 21 | this.commandContainer = commandContainer; 22 | this.defaultCommand = defaultCommand; 23 | this.errorHandler = errorHandler; 24 | } 25 | 26 | static StfCommander create(StfCommanderContext context, HelpCommandCreator commandCreator, ErrorHandler errorHandler, String... args) { 27 | CommandContainer commandContainer = new CommandContainer(context); 28 | JCommander commander = createCommander(commandContainer, args); 29 | final CommandContainer.Command helpCommand = commandCreator.createHelpCommand(commander); 30 | return new StfCommander(commander.getParsedCommand(), commandContainer, helpCommand, errorHandler); 31 | } 32 | 33 | private static JCommander createCommander(CommandContainer commandContainer, String[] args) { 34 | JCommander.Builder builder = JCommander.newBuilder(); 35 | for (String operation : commandContainer.getAllCommands()) { 36 | CommandContainer.Command command = commandContainer.getCommand(operation); 37 | builder.addCommand(operation, command); 38 | } 39 | JCommander commander = builder.build(); 40 | commander.setProgramName("stf"); 41 | commander.setCaseSensitiveOptions(false); 42 | commander.parseWithoutValidation(args); 43 | return commander; 44 | } 45 | 46 | void execute() { 47 | CommandContainer.Command command = chooseCommand(); 48 | if (command != null) { 49 | run(command.execute()); 50 | } else { 51 | errorHandler.handle(new UnknownCommandException(commandName)); 52 | } 53 | } 54 | 55 | private void run(Completable completable) { 56 | completable.subscribe(() -> {}, errorHandler::handle); 57 | } 58 | 59 | private CommandContainer.Command chooseCommand() { 60 | if (commandName == null) { 61 | return defaultCommand; 62 | } 63 | return commandContainer.getCommand(commandName); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/StfCommanderContext.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console; 2 | 3 | import com.github.e13mort.stf.client.FarmClient; 4 | import com.github.e13mort.stf.client.FarmInfo; 5 | import com.github.e13mort.stf.console.commands.cache.DeviceListCache; 6 | import io.reactivex.Single; 7 | 8 | import java.io.File; 9 | import java.io.FileReader; 10 | import java.io.IOException; 11 | import java.io.OutputStream; 12 | import java.util.Properties; 13 | import java.util.logging.Logger; 14 | 15 | public class StfCommanderContext { 16 | private static final String DEFAULT_PROPERTY_FILE_NAME = "farm.properties"; 17 | 18 | private final FarmClient client; 19 | private final AdbRunner adbRunner; 20 | private DeviceListCache cache; 21 | private OutputStream output; 22 | private Logger logger; 23 | 24 | StfCommanderContext(FarmClient client, AdbRunner adbRunner, DeviceListCache cache, OutputStream output, Logger logger) { 25 | this.client = client; 26 | this.adbRunner = adbRunner; 27 | this.cache = cache; 28 | this.output = output; 29 | this.logger = logger; 30 | } 31 | 32 | public FarmClient getClient() { 33 | return client; 34 | } 35 | 36 | public AdbRunner getAdbRunner() { 37 | return adbRunner; 38 | } 39 | 40 | public DeviceListCache getCache() { 41 | return cache; 42 | } 43 | 44 | public OutputStream getOutput() { 45 | return output; 46 | } 47 | 48 | public Logger getLogger() { 49 | return logger; 50 | } 51 | 52 | static Single create(Logger logger) { 53 | return Single.create(e -> e.onSuccess(createInternal(logger))); 54 | } 55 | 56 | private static StfCommanderContext createInternal(Logger logger) throws IOException { 57 | FarmInfo farmInfo = createFarmInfo(); 58 | FarmClient client = FarmClient.create(farmInfo); 59 | AdbRunner adbRunner = new AdbRunner(farmInfo.getSdkPath(), logger); 60 | DeviceListCache cache = DeviceListCache.getCache(); 61 | final OutputStream output = System.out; 62 | return new StfCommanderContext(client, adbRunner, cache, output, logger); 63 | } 64 | 65 | private static FarmInfo createFarmInfo() throws IOException { 66 | File properties = getPropertiesFile(DEFAULT_PROPERTY_FILE_NAME); 67 | return readFarmInfo(properties); 68 | } 69 | 70 | private static FarmInfo readFarmInfo(File propertiesFile) throws IOException { 71 | Properties properties = new Properties(); 72 | properties.load(new FileReader(propertiesFile)); 73 | String farmUrl = properties.getProperty("stf.url"); 74 | String apiKey = properties.getProperty("stf.key"); 75 | if (farmUrl == null || apiKey == null) { 76 | throw new IllegalArgumentException("Property file is invalid"); 77 | } 78 | String sdk = properties.getProperty("android_sdk"); 79 | int timeout = getTimeout(properties); 80 | return new FarmInfo(farmUrl, apiKey, sdk, timeout); 81 | } 82 | 83 | private static File getPropertiesFile(String propertiesFileName) { 84 | File file = new File(propertiesFileName); 85 | 86 | if (!file.exists()) { 87 | File homeFile = new File(System.getProperty("user.home"), propertiesFileName); 88 | if (homeFile.exists()) { 89 | return homeFile; 90 | } else { 91 | throw new IllegalArgumentException("Property file does not exists"); 92 | } 93 | } 94 | return file; 95 | } 96 | 97 | private static int getTimeout(Properties properties) { 98 | String timeoutProperty = properties.getProperty("stf.timeout"); 99 | return Integer.parseInt(timeoutProperty); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/CommandContainer.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands; 2 | 3 | import com.github.e13mort.stf.console.StfCommanderContext; 4 | import com.github.e13mort.stf.console.commands.connect.ConnectCommand; 5 | import com.github.e13mort.stf.console.commands.devices.DevicesCommand; 6 | import io.reactivex.Completable; 7 | 8 | import java.util.Collection; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public class CommandContainer { 13 | 14 | private Map commandMap = new HashMap<>(); 15 | 16 | public CommandContainer(StfCommanderContext c) { 17 | commandMap.put("devices", new DevicesCommand(c.getClient(), c.getCache(), c.getOutput())); 18 | commandMap.put("connect", new ConnectCommand(c.getClient(), c.getAdbRunner(), c.getCache(), c.getLogger())); 19 | commandMap.put("disconnect", new DisconnectCommand(c.getClient(), c.getLogger())); 20 | } 21 | 22 | public Command getCommand(String operation) { 23 | return commandMap.get(operation); 24 | } 25 | 26 | public Collection getAllCommands() { 27 | return commandMap.keySet(); 28 | } 29 | 30 | public interface Command { 31 | Completable execute(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/ConsoleDeviceParamsImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands; 2 | 3 | import com.beust.jcommander.Parameter; 4 | import com.github.e13mort.stf.adapter.filters.StringsFilterDescription; 5 | import com.github.e13mort.stf.client.parameters.DevicesParams; 6 | 7 | public class ConsoleDeviceParamsImpl implements DevicesParams { 8 | @Parameter(names = "--all", description = "Show all devices. By default only available devices are returned.") 9 | private boolean allDevices; 10 | @Parameter(names = "-abi", description = "Filter by device abi architecture") 11 | private String abi; 12 | @Parameter(names = "-api", description = "Filter by device api level") 13 | private int apiVersion; 14 | @Parameter(names = "-minApi", description = "Filter by device min api level") 15 | private int minApiVersion; 16 | @Parameter(names = "-maxApi", description = "Filter by device max api level") 17 | private int maxApiVersion; 18 | @Parameter(names = "-count", description = "Filter devices by count") 19 | private int count; 20 | @Parameter(names = "-name", description = "Filter devices by its name", converter = FilterDescriptionConverterImpl.class) 21 | private StringsFilterDescription nameFilterDescription; 22 | @Parameter(names = "-provider", description = "Filter devices by provider", converter = FilterDescriptionConverterImpl.class) 23 | private StringsFilterDescription providerFilterDescription; 24 | @Parameter(names = "-serial", description = "Filter devices by serial number", converter = FilterDescriptionConverterImpl.class) 25 | private StringsFilterDescription serialFilterDescription; 26 | 27 | @Override 28 | public boolean isAllDevices() { 29 | return allDevices; 30 | } 31 | 32 | @Override 33 | public String getAbi() { 34 | return abi; 35 | } 36 | 37 | @Override 38 | public int getApiVersion() { 39 | return apiVersion; 40 | } 41 | 42 | @Override 43 | public int getMinApiVersion() { 44 | return minApiVersion; 45 | } 46 | 47 | @Override 48 | public int getMaxApiVersion() { 49 | return maxApiVersion; 50 | } 51 | 52 | @Override 53 | public int getCount() { 54 | return count; 55 | } 56 | 57 | @Override 58 | public StringsFilterDescription getNameFilterDescription() { 59 | return nameFilterDescription; 60 | } 61 | 62 | @Override 63 | public StringsFilterDescription getProviderFilterDescription() { 64 | return providerFilterDescription; 65 | } 66 | 67 | @Override 68 | public StringsFilterDescription getSerialFilterDescription() { 69 | return serialFilterDescription; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/DisconnectCommand.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands; 2 | 3 | import com.beust.jcommander.Parameter; 4 | import com.beust.jcommander.Parameters; 5 | import com.github.e13mort.stf.client.FarmClient; 6 | import io.reactivex.Completable; 7 | import io.reactivex.Notification; 8 | import io.reactivex.annotations.NonNull; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.logging.Level; 13 | import java.util.logging.Logger; 14 | 15 | @Parameters(commandDescription = "Disconnect from all of currently connected devices") 16 | class DisconnectCommand implements CommandContainer.Command { 17 | private final FarmClient client; 18 | private final Logger logger; 19 | 20 | @Parameter(description = "Disconnect from the specified devices", names = "-s") 21 | private List serials = new ArrayList<>(); 22 | 23 | DisconnectCommand(FarmClient client, Logger logger) { 24 | this.client = client; 25 | this.logger = logger; 26 | } 27 | 28 | @Override 29 | public Completable execute() { 30 | if (serials.isEmpty()) return disconnectFromAll(); 31 | return disconnectFromDevices(serials); 32 | } 33 | 34 | private Completable disconnectFromDevices(List serials) { 35 | return Completable.fromPublisher(client.disconnectFromDevices(serials).doOnNext(this::handle)); 36 | } 37 | 38 | private Completable disconnectFromAll() { 39 | return Completable.fromPublisher(client.disconnectFromAllDevices().doOnNext(this::handle)); 40 | } 41 | 42 | private void handle(@NonNull Notification stringNotification) { 43 | logger.log(Level.INFO, "Disconnected from: {0}", stringNotification.getValue()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/EmptyDevicesException.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands; 2 | 3 | public class EmptyDevicesException extends Exception { } 4 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/FilterDescriptionConverterImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands; 2 | 3 | import com.beust.jcommander.IStringConverter; 4 | import com.github.e13mort.stf.adapter.filters.StringsFilterDescription; 5 | import com.github.e13mort.stf.adapter.filters.StringsFilterParser; 6 | 7 | public class FilterDescriptionConverterImpl implements IStringConverter { 8 | 9 | private StringsFilterParser parser = new StringsFilterParser(); 10 | 11 | @Override 12 | public StringsFilterDescription convert(String value) { 13 | return parser.parse(value); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/HelpCommand.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands; 2 | 3 | import com.beust.jcommander.JCommander; 4 | import io.reactivex.Completable; 5 | 6 | public class HelpCommand implements CommandContainer.Command { 7 | private final JCommander jCommander; 8 | 9 | public HelpCommand(JCommander jCommander) { 10 | this.jCommander = jCommander; 11 | } 12 | 13 | @Override 14 | public Completable execute() { 15 | return Completable.fromAction(jCommander::usage); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/HelpCommandCreator.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands; 2 | 3 | import com.beust.jcommander.JCommander; 4 | 5 | public interface HelpCommandCreator { 6 | CommandContainer.Command createHelpCommand(JCommander commander); 7 | 8 | class HelpCommandCreatorImpl implements HelpCommandCreator { 9 | @Override 10 | public CommandContainer.Command createHelpCommand(JCommander commander) { 11 | return new HelpCommand(commander); 12 | } 13 | } 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/UnknownCommandException.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands; 2 | 3 | public class UnknownCommandException extends Exception { 4 | public UnknownCommandException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/cache/DeviceListCache.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands.cache; 2 | 3 | import com.github.e13mort.stf.model.device.Device; 4 | 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | public interface DeviceListCache { 9 | static DeviceListCache getCache() { 10 | return new DeviceListCacheImpl(); 11 | } 12 | 13 | DeviceListCache EMPTY = new DeviceListCache() { 14 | @Override 15 | public CacheTransaction beginTransaction() { 16 | return CacheTransaction.EMPTY; 17 | } 18 | 19 | @Override 20 | public List getCachedFiles() { 21 | return Collections.emptyList(); 22 | } 23 | }; 24 | 25 | CacheTransaction beginTransaction(); 26 | 27 | List getCachedFiles(); 28 | 29 | interface CacheTransaction { 30 | CacheTransaction EMPTY = new CacheTransaction() { 31 | @Override 32 | public void addDevice(Device device) { 33 | 34 | } 35 | 36 | @Override 37 | public void save() { 38 | 39 | } 40 | }; 41 | 42 | void addDevice(Device device); 43 | 44 | void save(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/cache/DeviceListCacheImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands.cache; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.github.e13mort.stf.model.device.Device; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | public class DeviceListCacheImpl implements DeviceListCache { 14 | 15 | private static final String PATHNAME = System.getProperty("user.home") + File.separator + "stf-last-devices-command-result.json"; 16 | 17 | static class CacheTransactionImpl implements CacheTransaction { 18 | 19 | private List devices = new ArrayList<>(); 20 | 21 | @Override 22 | public void addDevice(Device device) { 23 | devices.add(device); 24 | } 25 | 26 | @Override 27 | public void save() { 28 | final ObjectMapper mapper = new ObjectMapper(); 29 | final File file = new File(PATHNAME); 30 | if (file.exists()) { 31 | file.delete(); 32 | } 33 | try { 34 | mapper.writeValue(file, devices); 35 | } catch (IOException e) { 36 | e.printStackTrace(); 37 | } 38 | } 39 | } 40 | 41 | @Override 42 | public CacheTransaction beginTransaction() { 43 | return new CacheTransactionImpl(); 44 | } 45 | 46 | @Override 47 | public List getCachedFiles() { 48 | final File file = new File(PATHNAME); 49 | if (!file.exists()) { 50 | return Collections.emptyList(); 51 | } 52 | final ObjectMapper mapper = new ObjectMapper(); 53 | Device[] devices; 54 | try { 55 | devices = mapper.readValue(file, Device[].class); 56 | } catch (IOException e) { 57 | devices = new Device[0]; 58 | } 59 | return Arrays.asList(devices); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/connect/CacheDevicesConnector.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands.connect; 2 | 3 | import com.github.e13mort.stf.adapter.filters.InclusionType; 4 | import com.github.e13mort.stf.adapter.filters.StringsFilterDescription; 5 | import com.github.e13mort.stf.client.FarmClient; 6 | import com.github.e13mort.stf.client.parameters.DevicesParams; 7 | import com.github.e13mort.stf.client.parameters.DevicesParamsImpl; 8 | import com.github.e13mort.stf.console.AdbRunner; 9 | import com.github.e13mort.stf.console.commands.cache.DeviceListCache; 10 | import com.github.e13mort.stf.model.device.Device; 11 | import io.reactivex.Flowable; 12 | import io.reactivex.Notification; 13 | import org.reactivestreams.Publisher; 14 | 15 | import java.util.List; 16 | import java.util.logging.Logger; 17 | 18 | class CacheDevicesConnector extends DeviceConnector { 19 | 20 | private final List devicesIndexesFromCache; 21 | private final DeviceListCache cache; 22 | 23 | protected CacheDevicesConnector(FarmClient client, AdbRunner runner, Logger logger, List devicesIndexesFromCache, DeviceListCache cache) { 24 | super(client, runner, logger); 25 | this.devicesIndexesFromCache = devicesIndexesFromCache; 26 | this.cache = cache; 27 | } 28 | 29 | @Override 30 | protected Publisher> createConnectionPublisher() { 31 | return readParamsFromCache(cache.getCachedFiles()).flatMap(this::connectWithParams); 32 | } 33 | 34 | private Flowable readParamsFromCache(List cachedFiles) { 35 | return Flowable.fromIterable(devicesIndexesFromCache) 36 | .doOnNext(integer -> validate(cachedFiles, integer)) 37 | .map(integer -> --integer) 38 | .map(cachedFiles::get) 39 | .map(Device::getSerial) 40 | .toList() 41 | .map(this::createStringsFilterDescription) 42 | .map(this::createDevicesParams) 43 | .toFlowable(); 44 | } 45 | 46 | private void validate(List cachedFiles, int index) throws InvalidCacheIndexException { 47 | if (!isIndexValid(cachedFiles, index)) { 48 | throw new InvalidCacheIndexException(index); 49 | } 50 | } 51 | 52 | private boolean isIndexValid(List cachedFiles, Integer integer) { 53 | return integer > 0 && integer <= cachedFiles.size(); 54 | } 55 | 56 | private StringsFilterDescription createStringsFilterDescription(List l) { 57 | return new StringsFilterDescription(InclusionType.INCLUDE, l); 58 | } 59 | 60 | private DevicesParams createDevicesParams(StringsFilterDescription filter) { 61 | final DevicesParamsImpl params = new DevicesParamsImpl(); 62 | params.setSerialFilterDescription(filter); 63 | return params; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/connect/ConnectCommand.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands.connect; 2 | 3 | import com.beust.jcommander.Parameter; 4 | import com.beust.jcommander.Parameters; 5 | import com.beust.jcommander.ParametersDelegate; 6 | import com.beust.jcommander.converters.FileConverter; 7 | import com.beust.jcommander.converters.IntegerConverter; 8 | import com.beust.jcommander.converters.URLConverter; 9 | import com.github.e13mort.stf.client.FarmClient; 10 | import com.github.e13mort.stf.console.AdbRunner; 11 | import com.github.e13mort.stf.console.commands.CommandContainer; 12 | import com.github.e13mort.stf.console.commands.ConsoleDeviceParamsImpl; 13 | import com.github.e13mort.stf.console.commands.cache.DeviceListCache; 14 | import io.reactivex.Completable; 15 | 16 | import java.io.File; 17 | import java.net.URL; 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.logging.Logger; 21 | 22 | @Parameters(commandDescription = "Connect to devices") 23 | public class ConnectCommand implements CommandContainer.Command { 24 | private final FarmClient client; 25 | private final AdbRunner adbRunner; 26 | private final Logger logger; 27 | 28 | @ParametersDelegate 29 | private ConsoleDeviceParamsImpl params = new ConsoleDeviceParamsImpl(); 30 | @Parameter(names = "--my", description = "Connect to currently taken devices") 31 | private Boolean connectToMyDevices = false; 32 | @Parameter(names = "-l", variableArity = true, listConverter = IntegerConverter.class, 33 | description = "Connect to devices by its indexes from the results of previous \"devices\" command. E.g. \"-l 1 2 5\"") 34 | private List devicesIndexesFromCache = new ArrayList<>(); 35 | @Parameter(names = "-f", description = "Read connection params from a file", converter = FileConverter.class) 36 | private File storedConnectionParamsFile; 37 | @Parameter(names = "-u", description = "Read connection params from an url", converter = URLConverter.class) 38 | private URL storedConnectionParamsUrl; 39 | private DeviceListCache cache; 40 | 41 | public ConnectCommand(FarmClient client, AdbRunner adbRunner, DeviceListCache cache, Logger logger) { 42 | this.client = client; 43 | this.adbRunner = adbRunner; 44 | this.cache = cache; 45 | this.logger = logger; 46 | } 47 | 48 | @Override 49 | public Completable execute() { 50 | DeviceConnector connector = chooseConnector(); 51 | return connector.connect(); 52 | } 53 | 54 | void setDevicesIndexesFromCache(List devicesIndexesFromCache) { 55 | this.devicesIndexesFromCache = devicesIndexesFromCache; 56 | } 57 | 58 | private DeviceConnector chooseConnector() { 59 | DeviceConnector connector; 60 | if (connectToMyDevices) { 61 | connector = new MyDevicesConnector(client, adbRunner, logger); 62 | } else if (!devicesIndexesFromCache.isEmpty()) { 63 | connector = new CacheDevicesConnector(client, adbRunner, logger, devicesIndexesFromCache, cache); 64 | } else if (storedConnectionParamsFile != null) { 65 | connector = FileParamsDeviceConnector.of(client, adbRunner, logger, storedConnectionParamsFile); 66 | } else if (storedConnectionParamsUrl != null) { 67 | connector = FileParamsDeviceConnector.of(client, adbRunner, logger, storedConnectionParamsUrl); 68 | } else { 69 | connector = new ParamsConnector(client, adbRunner, logger, params); 70 | } 71 | return connector; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/connect/DeviceConnector.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands.connect; 2 | 3 | import com.github.e13mort.stf.client.parameters.DevicesParams; 4 | import com.github.e13mort.stf.client.FarmClient; 5 | import com.github.e13mort.stf.console.AdbRunner; 6 | import com.github.e13mort.stf.console.commands.EmptyDevicesException; 7 | import io.reactivex.Completable; 8 | import io.reactivex.Flowable; 9 | import io.reactivex.Notification; 10 | import org.reactivestreams.Publisher; 11 | 12 | import java.io.IOException; 13 | import java.util.logging.Level; 14 | import java.util.logging.Logger; 15 | 16 | abstract class DeviceConnector { 17 | protected final FarmClient client; 18 | protected final AdbRunner runner; 19 | protected final Logger logger; 20 | 21 | protected DeviceConnector(FarmClient client, AdbRunner runner, Logger logger) { 22 | this.client = client; 23 | this.runner = runner; 24 | this.logger = logger; 25 | } 26 | 27 | Completable connect() { 28 | return Completable.fromPublisher(createConnectionPublisher()); 29 | } 30 | 31 | protected abstract Publisher> createConnectionPublisher(); 32 | 33 | protected Flowable> getEmptyError() { 34 | return Flowable.error(new EmptyDevicesException()); 35 | } 36 | 37 | protected void handleDevices(Notification deviceNotification) { 38 | if (deviceNotification.isOnNext()) { 39 | handleConnectedDevice(deviceNotification.getValue()); 40 | } else if (deviceNotification.isOnError()) { 41 | handleNotConnectedDevice(deviceNotification.getError()); 42 | } 43 | } 44 | 45 | protected void handleConnectedDevice(String deviceIp) { 46 | try { 47 | runner.connectToDevice(deviceIp); 48 | } catch (IOException e) { 49 | logger.log(Level.WARNING, "Failed to connect to a device", e); 50 | } 51 | } 52 | 53 | protected void handleNotConnectedDevice(Throwable error) { 54 | logger.log(Level.WARNING, "Failed to lock a device", error); 55 | } 56 | 57 | protected Flowable> connectWithParams(DevicesParams params) { 58 | return client.connectToDevices(params) 59 | .doOnNext(this::handleDevices) 60 | .switchIfEmpty(getEmptyError()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/connect/FileParamsDeviceConnector.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands.connect; 2 | 3 | import com.github.e13mort.stf.client.FarmClient; 4 | import com.github.e13mort.stf.client.parameters.DevicesParams; 5 | import com.github.e13mort.stf.client.parameters.JsonDeviceParametersReader; 6 | import com.github.e13mort.stf.console.AdbRunner; 7 | 8 | import org.reactivestreams.Publisher; 9 | 10 | import java.io.File; 11 | import java.net.URL; 12 | import java.util.logging.Logger; 13 | 14 | import io.reactivex.Flowable; 15 | import io.reactivex.Notification; 16 | 17 | class FileParamsDeviceConnector extends DeviceConnector { 18 | 19 | private final ParametersReader reader; 20 | 21 | static DeviceConnector of(FarmClient client, AdbRunner adbRunner, Logger logger, File paramsFile) { 22 | return new FileParamsDeviceConnector(client, adbRunner, logger, () -> new JsonDeviceParametersReader().read(paramsFile)); 23 | } 24 | 25 | static DeviceConnector of(FarmClient client, AdbRunner adbRunner, Logger logger, URL paramsUrl) { 26 | return new FileParamsDeviceConnector(client, adbRunner, logger, () -> new JsonDeviceParametersReader().read(paramsUrl)); 27 | } 28 | 29 | private FileParamsDeviceConnector(FarmClient client, AdbRunner adbRunner, Logger logger, ParametersReader reader) { 30 | super(client, adbRunner, logger); 31 | this.reader = reader; 32 | } 33 | 34 | @Override 35 | protected Publisher> createConnectionPublisher() { 36 | try { 37 | return connectWithParams(reader.read()); 38 | } catch (JsonDeviceParametersReader.JsonParamsReaderException e) { 39 | return Flowable.error(e); 40 | } 41 | } 42 | 43 | private interface ParametersReader { 44 | DevicesParams read() throws JsonDeviceParametersReader.JsonParamsReaderException; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/connect/InvalidCacheIndexException.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands.connect; 2 | 3 | public class InvalidCacheIndexException extends Exception { 4 | private final int index; 5 | 6 | public InvalidCacheIndexException(int index) { 7 | super("Invalid cache index: " + index); 8 | this.index = index; 9 | } 10 | 11 | public int getIndex() { 12 | return index; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/connect/MyDevicesConnector.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands.connect; 2 | 3 | import com.github.e13mort.stf.client.FarmClient; 4 | import com.github.e13mort.stf.console.AdbRunner; 5 | import io.reactivex.Flowable; 6 | import io.reactivex.Notification; 7 | import org.reactivestreams.Publisher; 8 | 9 | import java.util.logging.Logger; 10 | 11 | class MyDevicesConnector extends DeviceConnector { 12 | 13 | public MyDevicesConnector(FarmClient client, AdbRunner runner, Logger logger) { 14 | super(client, runner, logger); 15 | } 16 | 17 | @Override 18 | protected Publisher> createConnectionPublisher() { 19 | return connectToMyDevices(); 20 | } 21 | 22 | private Flowable> connectToMyDevices() { 23 | return client.getMyDevices() 24 | .map(device -> Notification.createOnNext((String) device.getRemoteConnectUrl())) 25 | .switchIfEmpty(getEmptyError()) 26 | .doOnNext(this::handleDevices); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/connect/ParamsConnector.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands.connect; 2 | 3 | import com.github.e13mort.stf.client.FarmClient; 4 | import com.github.e13mort.stf.client.parameters.DevicesParams; 5 | import com.github.e13mort.stf.console.AdbRunner; 6 | import io.reactivex.Notification; 7 | import org.reactivestreams.Publisher; 8 | 9 | import java.util.logging.Logger; 10 | 11 | class ParamsConnector extends DeviceConnector { 12 | 13 | private final DevicesParams params; 14 | 15 | protected ParamsConnector(FarmClient client, AdbRunner runner, Logger logger, DevicesParams params) { 16 | super(client, runner, logger); 17 | this.params = params; 18 | } 19 | 20 | @Override 21 | protected Publisher> createConnectionPublisher() { 22 | return connectWithParams(params); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/devices/DeviceMapper.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands.devices; 2 | 3 | import com.github.e13mort.stf.model.device.Device; 4 | import io.reactivex.functions.Function; 5 | 6 | import java.util.Collection; 7 | import java.util.Collections; 8 | 9 | interface DeviceMapper extends Function> { 10 | DeviceMapper EMPTY = new DeviceMapper() { 11 | @Override 12 | public Collection getColumnNames() { 13 | return Collections.emptyList(); 14 | } 15 | 16 | @Override 17 | public Collection apply(Device device) throws Exception { 18 | return Collections.emptyList(); 19 | } 20 | }; 21 | 22 | Collection getColumnNames(); 23 | } 24 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/devices/DevicesCommand.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands.devices; 2 | 3 | import com.beust.jcommander.Parameter; 4 | import com.beust.jcommander.Parameters; 5 | import com.beust.jcommander.ParametersDelegate; 6 | import com.github.e13mort.stf.client.FarmClient; 7 | import com.github.e13mort.stf.client.parameters.DevicesParams; 8 | import com.github.e13mort.stf.console.commands.CommandContainer; 9 | import com.github.e13mort.stf.console.commands.ConsoleDeviceParamsImpl; 10 | import com.github.e13mort.stf.console.commands.cache.DeviceListCache; 11 | import io.reactivex.Completable; 12 | 13 | import java.io.OutputStream; 14 | 15 | @Parameters(commandDescription = "Print list of available devices") 16 | public class DevicesCommand implements CommandContainer.Command { 17 | private final FarmClient client; 18 | private final OutputStream output; 19 | 20 | @ParametersDelegate 21 | private DevicesParams params = new ConsoleDeviceParamsImpl(); 22 | 23 | @Parameter(names = {"--my-columns"}, description = " Use columns from web panel") 24 | private boolean userColumns; 25 | private DeviceListCache cache; 26 | 27 | public DevicesCommand(FarmClient client, DeviceListCache cache, OutputStream output) { 28 | this.client = client; 29 | this.cache = cache; 30 | this.output = output; 31 | } 32 | 33 | @Override 34 | public Completable execute() { 35 | DocumentsLoader loader = new DocumentsLoader(client, params); 36 | loader.setFieldsReader(userColumns ? new ReflectionDeviceReader() : new PredefinedDeviceReader()); 37 | loader.setDeviceListCache(cache); 38 | 39 | TablePrinter tablePrinter = new TablePrinter(loader.getColumnNames()); 40 | return Completable.fromPublisher( 41 | loader.loadDevices() 42 | .doOnNext(tablePrinter::addDevice) 43 | .doOnComplete(() -> tablePrinter.print(output))); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/devices/DocumentsLoader.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands.devices; 2 | 3 | import com.github.e13mort.stf.client.FarmClient; 4 | import com.github.e13mort.stf.client.parameters.DevicesParams; 5 | import com.github.e13mort.stf.console.commands.cache.DeviceListCache; 6 | import io.reactivex.Flowable; 7 | 8 | import java.util.Collection; 9 | 10 | class DocumentsLoader { 11 | 12 | private final FarmClient client; 13 | private final DevicesParams params; 14 | private DeviceMapper fieldsReader = DeviceMapper.EMPTY; 15 | private DeviceListCache deviceListCache = DeviceListCache.EMPTY; 16 | 17 | public DocumentsLoader(FarmClient client, DevicesParams params) { 18 | this.client = client; 19 | this.params = params; 20 | } 21 | 22 | public void setFieldsReader(DeviceMapper fieldsReader) { 23 | this.fieldsReader = fieldsReader; 24 | } 25 | 26 | public Collection getColumnNames() { 27 | return fieldsReader.getColumnNames(); 28 | } 29 | 30 | Flowable> loadDevices() { 31 | final DeviceListCache.CacheTransaction transaction = deviceListCache.beginTransaction(); 32 | return client 33 | .getDevices(params) 34 | .doOnNext(transaction::addDevice) 35 | .map(fieldsReader) 36 | .doOnComplete(transaction::save); 37 | } 38 | 39 | public void setDeviceListCache(DeviceListCache deviceListCache) { 40 | this.deviceListCache = deviceListCache; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/devices/PredefinedDeviceReader.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands.devices; 2 | 3 | import com.github.e13mort.stf.model.device.Device; 4 | 5 | import java.util.Arrays; 6 | import java.util.Collection; 7 | import java.util.List; 8 | 9 | final class PredefinedDeviceReader implements DeviceMapper { 10 | 11 | private static final List PREDEFINED_COLUMN_NAMES = Arrays.asList("Name", "Abi", "Serial", "Sdk", "Provider"); 12 | 13 | @Override 14 | public Collection apply(Device device) throws Exception { 15 | return Arrays.asList( 16 | device.getModel(), 17 | device.getAbi(), 18 | device.getSerial(), 19 | String.valueOf(device.getSdk()), 20 | device.getProvider().getName() 21 | ); 22 | } 23 | 24 | @Override 25 | public Collection getColumnNames() { 26 | return PREDEFINED_COLUMN_NAMES; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/devices/ReflectionDeviceReader.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands.devices; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.databind.ObjectWriter; 6 | import com.github.e13mort.stf.model.device.Device; 7 | 8 | import java.io.StringWriter; 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.Collection; 12 | 13 | final class ReflectionDeviceReader implements DeviceMapper { 14 | 15 | //TODO implement user columns retrieving from farm client 16 | 17 | private final ObjectMapper mapper = new ObjectMapper(); 18 | private final ObjectWriter writer = mapper.writerFor(Device.class); 19 | private final String[] names = new String[]{"model", "abi", "serial", "sdk", "provider"}; 20 | 21 | @Override 22 | public Collection apply(Device device) throws Exception { 23 | StringWriter stringWriter = new StringWriter(); 24 | writer.writeValue(stringWriter, device); 25 | 26 | JsonNode node = mapper.readTree(stringWriter.toString()); 27 | Collection strings = new ArrayList<>(); 28 | for (String s : names) { 29 | JsonNode jsonNode = node.get(s); 30 | strings.add(jsonNode.asText("invalid")); 31 | } 32 | return strings; 33 | } 34 | 35 | @Override 36 | public Collection getColumnNames() { 37 | return Arrays.asList(names); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /client/src/main/java/com/github/e13mort/stf/console/commands/devices/TablePrinter.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands.devices; 2 | 3 | import de.vandermeer.asciitable.v2.RenderedTable; 4 | import de.vandermeer.asciitable.v2.V2_AsciiTable; 5 | import de.vandermeer.asciitable.v2.render.V2_AsciiTableRenderer; 6 | import de.vandermeer.asciitable.v2.render.WidthLongestLine; 7 | import de.vandermeer.asciitable.v2.row.ContentRow; 8 | 9 | import java.io.OutputStream; 10 | import java.io.PrintStream; 11 | import java.util.ArrayList; 12 | import java.util.Arrays; 13 | import java.util.Collection; 14 | import java.util.List; 15 | 16 | class TablePrinter { 17 | 18 | private static final String NUMBER_HEADER = "#"; 19 | private final V2_AsciiTable table; 20 | private int rowCounter = 1; 21 | 22 | public TablePrinter(Collection columnNames) { 23 | table = prepareTable(columnNames); 24 | } 25 | 26 | public V2_AsciiTable addDevice(Collection strings) throws Exception { 27 | table.addRow(addFirstElement(strings, String.valueOf(rowCounter++))); 28 | table.addRule(); 29 | return table; 30 | } 31 | 32 | public void print(OutputStream outputStream) { 33 | V2_AsciiTableRenderer renderer = new V2_AsciiTableRenderer(); 34 | renderer.setWidth(new WidthLongestLine()); 35 | RenderedTable render = renderer.render(table); 36 | new PrintStream(outputStream).println(render.toString()); 37 | } 38 | 39 | private V2_AsciiTable prepareTable(Collection columnNames) { 40 | V2_AsciiTable table = new V2_AsciiTable(); 41 | table.addStrongRule(); 42 | List headerContent = addFirstElement(columnNames, NUMBER_HEADER); 43 | printHeader(table, headerContent); 44 | table.addStrongRule(); 45 | return table; 46 | } 47 | 48 | private void printHeader(V2_AsciiTable table, Collection columnNames) { 49 | ContentRow header = table.addRow(columnNames); 50 | char[] chars = new char[columnNames.size()]; 51 | Arrays.fill(chars, 'c'); 52 | header.setAlignment(chars); 53 | } 54 | 55 | private List addFirstElement(Collection target, String element) { 56 | final ArrayList content = new ArrayList<>(target); 57 | content.add(0, element); 58 | return content; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /client/src/test/java/com/github/e13mort/stf/console/BaseStfCommanderTest.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console; 2 | 3 | import com.beust.jcommander.JCommander; 4 | import com.github.e13mort.stf.client.FarmClient; 5 | import com.github.e13mort.stf.client.parameters.DevicesParams; 6 | import com.github.e13mort.stf.console.commands.CommandContainer; 7 | import com.github.e13mort.stf.console.commands.HelpCommandCreator; 8 | import com.github.e13mort.stf.console.commands.UnknownCommandException; 9 | import com.github.e13mort.stf.console.commands.cache.DeviceListCache; 10 | import com.github.e13mort.stf.model.device.Device; 11 | import io.reactivex.Completable; 12 | import io.reactivex.Flowable; 13 | import org.junit.jupiter.api.BeforeEach; 14 | import org.junit.jupiter.api.function.Executable; 15 | import org.mockito.ArgumentCaptor; 16 | import org.mockito.Mock; 17 | import org.mockito.MockitoAnnotations; 18 | 19 | import java.io.IOException; 20 | import java.io.OutputStream; 21 | import java.util.Collections; 22 | import java.util.logging.Logger; 23 | 24 | import static org.mockito.ArgumentMatchers.any; 25 | import static org.mockito.Mockito.verify; 26 | import static org.mockito.Mockito.when; 27 | 28 | public class BaseStfCommanderTest { 29 | protected static final String TEST_DEVICE_REMOTE = "127.0.0.1:15500"; 30 | @Mock 31 | protected FarmClient farmClient; 32 | @Mock 33 | protected AdbRunner adbRunner; 34 | @Mock 35 | protected CommandContainer.Command helpCommand; 36 | @Mock 37 | protected DeviceListCache cache; 38 | @Mock 39 | protected ErrorHandler errorHandler; 40 | @Mock 41 | protected DeviceListCache.CacheTransaction cacheTransaction; 42 | @Mock 43 | private HelpCommandCreator helpCommandCreator; 44 | @Mock 45 | protected OutputStream outputStream; 46 | @Mock 47 | protected Logger logger; 48 | @Mock 49 | private Device myDevice; 50 | 51 | @BeforeEach 52 | protected void setUp() throws IOException { 53 | MockitoAnnotations.initMocks(this); 54 | when(farmClient.getDevices(any(DevicesParams.class))).thenReturn(Flowable.empty()); 55 | when(farmClient.connectToDevices(any(DevicesParams.class))).thenReturn(Flowable.empty()); 56 | when(farmClient.disconnectFromAllDevices()).thenReturn(Flowable.empty()); 57 | when(helpCommand.execute()).thenReturn(Completable.complete()); 58 | when(helpCommandCreator.createHelpCommand(any(JCommander.class))).thenReturn(helpCommand); 59 | when(farmClient.getMyDevices()).thenReturn(Flowable.fromArray(myDevice)); 60 | when(myDevice.getRemoteConnectUrl()).thenReturn(TEST_DEVICE_REMOTE); 61 | when(cache.beginTransaction()).thenReturn(cacheTransaction); 62 | when(cache.getCachedFiles()).thenReturn(Collections.emptyList()); 63 | } 64 | 65 | protected DevicesParams runDeviceParamsTest(DeviceParamsProducingCommand source, String params) throws IOException, UnknownCommandException { 66 | ArgumentCaptor objectArgumentCaptor = ArgumentCaptor.forClass(DevicesParams.class); 67 | createCommander(source.name().toLowerCase() + " " + params).execute(); 68 | if (source == DeviceParamsProducingCommand.DEVICES) { 69 | verify(farmClient).getDevices(objectArgumentCaptor.capture()); 70 | } else { 71 | verify(farmClient).connectToDevices(objectArgumentCaptor.capture()); 72 | } 73 | return objectArgumentCaptor.getValue(); 74 | } 75 | 76 | protected Executable test(final DeviceParamsProducingCommand source, final String str) { 77 | return () -> runDeviceParamsTest(source, str); 78 | } 79 | 80 | protected StfCommander createCommander(String str) throws IOException { 81 | return StfCommander.create(new StfCommanderContext(farmClient, adbRunner, cache, outputStream, logger), helpCommandCreator, errorHandler, str.split(" ")); 82 | } 83 | 84 | enum DeviceParamsProducingCommand {DEVICES, CONNECT} 85 | } 86 | -------------------------------------------------------------------------------- /client/src/test/java/com/github/e13mort/stf/console/StfCommanderTest.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console; 2 | 3 | import com.beust.jcommander.ParameterException; 4 | import com.github.e13mort.stf.adapter.filters.StringsFilterDescription; 5 | import com.github.e13mort.stf.client.parameters.DevicesParams; 6 | import com.github.e13mort.stf.console.commands.UnknownCommandException; 7 | 8 | import io.reactivex.Flowable; 9 | import org.junit.jupiter.api.DisplayName; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.params.ParameterizedTest; 12 | import org.junit.jupiter.params.provider.EnumSource; 13 | 14 | import java.io.IOException; 15 | import java.util.Arrays; 16 | 17 | import static java.util.Arrays.asList; 18 | import static java.util.Collections.singletonList; 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | import static org.junit.jupiter.api.Assertions.assertFalse; 21 | import static org.junit.jupiter.api.Assertions.assertLinesMatch; 22 | import static org.junit.jupiter.api.Assertions.assertNotNull; 23 | import static org.junit.jupiter.api.Assertions.assertNull; 24 | import static org.junit.jupiter.api.Assertions.assertThrows; 25 | import static org.junit.jupiter.api.Assertions.assertTrue; 26 | import static org.mockito.ArgumentMatchers.any; 27 | import static org.mockito.ArgumentMatchers.eq; 28 | import static org.mockito.Mockito.verify; 29 | import static org.mockito.Mockito.when; 30 | 31 | class StfCommanderTest extends BaseStfCommanderTest { 32 | 33 | @DisplayName("Command without params should be called with non null DeviceParams object") 34 | @ParameterizedTest(name = "Command is {0}") 35 | @EnumSource(DeviceParamsProducingCommand.class) 36 | void commandsEmptyParamsNonNullResult(DeviceParamsProducingCommand source) throws Exception { 37 | DevicesParams value = runDeviceParamsTest(source, ""); 38 | assertNotNull(value); 39 | } 40 | 41 | @DisplayName("Command without params should be called with an empty DeviceParams object") 42 | @ParameterizedTest(name = "Command is {0}") 43 | @EnumSource(DeviceParamsProducingCommand.class) 44 | void commandsEmptyParamsNullableFields(DeviceParamsProducingCommand source) throws Exception { 45 | DevicesParams value = runDeviceParamsTest(source, ""); 46 | assertNull(value.getAbi()); 47 | assertNull(value.getNameFilterDescription()); 48 | assertNull(value.getProviderFilterDescription()); 49 | assertNull(value.getSerialFilterDescription()); 50 | assertEquals(0, value.getApiVersion()); 51 | assertEquals(0, value.getMaxApiVersion()); 52 | assertEquals(0, value.getMinApiVersion()); 53 | assertEquals(0, value.getCount()); 54 | assertFalse(value.isAllDevices()); 55 | } 56 | 57 | @DisplayName("test_abi is parsed in command") 58 | @ParameterizedTest(name = "Command is {0}") 59 | @EnumSource(DeviceParamsProducingCommand.class) 60 | void testReadAbiFromValidString(DeviceParamsProducingCommand source) throws Exception { 61 | DevicesParams params = runDeviceParamsTest(source, "-abi test_abi"); 62 | assertEquals("test_abi", params.getAbi()); 63 | } 64 | 65 | @DisplayName("test_abi is parsed in command") 66 | @ParameterizedTest(name = "Command is {0}") 67 | @EnumSource(DeviceParamsProducingCommand.class) 68 | void testAllDevicesEnabledByFlag(DeviceParamsProducingCommand source) throws Exception { 69 | DevicesParams params = runDeviceParamsTest(source, "--all"); 70 | assertTrue(params.isAllDevices()); 71 | } 72 | 73 | @DisplayName("Unspecified api in command will set it to 0") 74 | @ParameterizedTest(name = "Command is {0}") 75 | @EnumSource(DeviceParamsProducingCommand.class) 76 | void testApiVersionIsZeroInUnrelatedString(DeviceParamsProducingCommand source) throws Exception { 77 | DevicesParams devicesParams = runDeviceParamsTest(source, ""); 78 | assertEquals(0, devicesParams.getApiVersion()); 79 | } 80 | 81 | @DisplayName("Specified api in command will set it up") 82 | @ParameterizedTest(name = "Command is {0}") 83 | @EnumSource(DeviceParamsProducingCommand.class) 84 | void testApiVersionIsValidInParameter(DeviceParamsProducingCommand source) throws Exception { 85 | DevicesParams params = runDeviceParamsTest(source, "-api 19"); 86 | assertEquals(19, params.getApiVersion()); 87 | } 88 | 89 | @DisplayName("Min api = 19 is parsed in command") 90 | @ParameterizedTest(name = "Command is {0}") 91 | @EnumSource(DeviceParamsProducingCommand.class) 92 | void testMinApiVersionIsValidInParameter(DeviceParamsProducingCommand source) throws Exception { 93 | DevicesParams params = runDeviceParamsTest(source, "-minApi 19"); 94 | assertEquals(19, params.getMinApiVersion()); 95 | } 96 | 97 | @DisplayName("Max api = 19 is parsed in command") 98 | @ParameterizedTest(name = "Command is {0}") 99 | @EnumSource(DeviceParamsProducingCommand.class) 100 | void testMaxApiVersionIsValidInParameter(DeviceParamsProducingCommand source) throws Exception { 101 | DevicesParams params = runDeviceParamsTest(source, "-maxApi 19"); 102 | assertEquals(19, params.getMaxApiVersion()); 103 | } 104 | 105 | @DisplayName("Max api = not_a_number is failed to be parsed in command") 106 | @ParameterizedTest(name = "Command is {0}") 107 | @EnumSource(DeviceParamsProducingCommand.class) 108 | void testExceptionIsThrownWhenApiIsInvalid(DeviceParamsProducingCommand source) { 109 | assertThrows(ParameterException.class, test(source, "-api not_a_number")); 110 | } 111 | 112 | @DisplayName("Count = 10 is parsed in command") 113 | @ParameterizedTest(name = "Command is {0}") 114 | @EnumSource(DeviceParamsProducingCommand.class) 115 | void testCountPropertyIsValid(DeviceParamsProducingCommand source) throws Exception { 116 | DevicesParams params = runDeviceParamsTest(source, "-count 10"); 117 | assertEquals(10, params.getCount()); 118 | } 119 | 120 | @DisplayName("Count = not_a_number is failed to be parsed in command") 121 | @ParameterizedTest(name = "Command is {0}") 122 | @EnumSource(DeviceParamsProducingCommand.class) 123 | void testInvalidCountPropertyThrowsAnException(DeviceParamsProducingCommand source) { 124 | assertThrows(ParameterException.class, test(source, "-count not_a_number")); 125 | } 126 | 127 | @DisplayName("Void count is failed to be parsed in command") 128 | @ParameterizedTest(name = "Command is {0}") 129 | @EnumSource(DeviceParamsProducingCommand.class) 130 | void testVoidCountPropertyThrowsAnException(DeviceParamsProducingCommand source) { 131 | assertThrows(Exception.class, test(source, "-count -l")); 132 | } 133 | 134 | @DisplayName("Name = name is parsed in command") 135 | @ParameterizedTest(name = "Command is {0}") 136 | @EnumSource(DeviceParamsProducingCommand.class) 137 | void testNamePropertyIsValid(DeviceParamsProducingCommand source) throws Exception { 138 | DevicesParams deviceParams = runDeviceParamsTest(source, "-name name"); 139 | StringsFilterDescription description = deviceParams.getNameFilterDescription(); 140 | assertLinesMatch(singletonList("name"), description.getTemplates()); 141 | } 142 | 143 | @DisplayName("Name = name1,name2 is parsed in command") 144 | @ParameterizedTest(name = "Command is {0}") 145 | @EnumSource(DeviceParamsProducingCommand.class) 146 | void testFewNamesPropertyIsValid(DeviceParamsProducingCommand source) throws Exception { 147 | DevicesParams deviceParams = runDeviceParamsTest(source, "-name name1,name2"); 148 | StringsFilterDescription description = deviceParams.getNameFilterDescription(); 149 | assertLinesMatch(asList("name1", "name2"), description.getTemplates()); 150 | } 151 | 152 | @DisplayName("Void name is null in command") 153 | @ParameterizedTest(name = "Command is {0}") 154 | @EnumSource(DeviceParamsProducingCommand.class) 155 | void testVoidNameThrowsAnException(DeviceParamsProducingCommand source) { 156 | assertThrows(Exception.class, test(source, "-n")); 157 | } 158 | 159 | @DisplayName("Call app without any param will execute the help command") 160 | @Test 161 | void testEmptyCommandParamsWillExecuteHelp() throws Exception { 162 | createCommander("").execute(); 163 | verify(helpCommand).execute(); 164 | } 165 | 166 | @DisplayName("Call app without any param will execute the help command") 167 | @Test 168 | void test() throws Exception { 169 | createCommander("unknown").execute(); 170 | verify(helpCommand).execute(); 171 | } 172 | 173 | @DisplayName("Provider = p1 is parsed in command") 174 | @ParameterizedTest(name = "Command is {0}") 175 | @EnumSource(DeviceParamsProducingCommand.class) 176 | void testProviderDescriptionNotNullWithParameter(DeviceParamsProducingCommand source) throws Exception { 177 | DevicesParams params = runDeviceParamsTest(source, "-provider p1"); 178 | assertNotNull(params.getProviderFilterDescription()); 179 | } 180 | 181 | @DisplayName("Serial = serial1,serial2 is parsed in command") 182 | @ParameterizedTest(name = "Command is {0}") 183 | @EnumSource(DeviceParamsProducingCommand.class) 184 | void testSerialNumberDescriptionNotNullWithParameter(DeviceParamsProducingCommand source) throws IOException, UnknownCommandException { 185 | DevicesParams params = runDeviceParamsTest(source, "-serial serial1,serial2"); 186 | assertNotNull(params.getSerialFilterDescription()); 187 | } 188 | 189 | @DisplayName("Command connect with '--my' parameter will connect to an active device") 190 | @Test 191 | void testConnectToMyDevice() throws IOException { 192 | createCommander("connect --my").execute(); 193 | verify(adbRunner).connectToDevice(eq(TEST_DEVICE_REMOTE)); 194 | } 195 | 196 | @DisplayName("connect command is parsed with valid -l param") 197 | @Test 198 | void testConnectCommandWithValidCacheParam() throws IOException { 199 | createCommander("connect -l 1 2 3"); 200 | } 201 | 202 | @DisplayName("connect command will throw the ParameterException if -l param is invalid") 203 | @Test 204 | void testConnectCommandWithInvalidValidCacheParam() { 205 | assertThrows(ParameterException.class, () -> createCommander("connect -l str")); 206 | } 207 | 208 | @DisplayName("Connect command with valid -u parameter will be parsed successfully") 209 | @Test 210 | void testValidUrlParamsIsParsed() throws IOException { 211 | createCommander("connect -u http://google.com"); 212 | } 213 | 214 | @DisplayName("Connect command with valid -f parameter will be parsed successfully") 215 | @Test 216 | void testValidFileParamsIsParsed() throws IOException { 217 | createCommander("connect -f some/file.json"); 218 | } 219 | 220 | @DisplayName("Connect command with invalid -u parameter will throw an error") 221 | @Test 222 | void testInvalidUrlParamsIsFailedToParse() { 223 | assertThrows(ParameterException.class, () -> createCommander(" connect -u not_a_url")); 224 | } 225 | 226 | @DisplayName("Connect command with empty -u parameter will throw an error") 227 | @Test 228 | void testEmptyUrlParamsIsFailedToParse() { 229 | assertThrows(ParameterException.class, () -> createCommander(" connect -u")); 230 | } 231 | 232 | @DisplayName("Connect command with empty -f parameter will throw an error") 233 | @Test 234 | void testEmptyFileParamsIsFailedToParse() { 235 | assertThrows(ParameterException.class, () -> createCommander(" connect -u")); 236 | } 237 | 238 | @DisplayName("Disconnect command call disconnectFromAllDevices method") 239 | @Test 240 | void testDisconnectCommand() throws IOException { 241 | createCommander("disconnect").execute(); 242 | verify(farmClient).disconnectFromAllDevices(); 243 | } 244 | 245 | @DisplayName("Disconnect command with -s will call disconnectFromDevices method with the same parameters") 246 | @Test 247 | void testDisconnectFromSpecifiedDevices() throws IOException { 248 | when(farmClient.disconnectFromDevices(any())).thenReturn(Flowable.empty()); 249 | createCommander("disconnect -s ser1,ser2").execute(); 250 | verify(farmClient).disconnectFromDevices(eq(Arrays.asList("ser1", "ser2"))); 251 | } 252 | } -------------------------------------------------------------------------------- /client/src/test/java/com/github/e13mort/stf/console/commands/connect/ConnectCommandTest.java: -------------------------------------------------------------------------------- 1 | package com.github.e13mort.stf.console.commands.connect; 2 | 3 | import com.github.e13mort.stf.client.parameters.DevicesParams; 4 | import com.github.e13mort.stf.console.BaseStfCommanderTest; 5 | import com.github.e13mort.stf.model.device.Device; 6 | 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.DisplayName; 9 | import org.junit.jupiter.params.ParameterizedTest; 10 | import org.junit.jupiter.params.provider.Arguments; 11 | import org.junit.jupiter.params.provider.MethodSource; 12 | import org.mockito.ArgumentCaptor; 13 | 14 | import java.io.IOException; 15 | import java.util.ArrayList; 16 | import java.util.Arrays; 17 | import java.util.Collections; 18 | import java.util.List; 19 | import java.util.stream.Stream; 20 | 21 | import io.reactivex.Flowable; 22 | import io.reactivex.Notification; 23 | 24 | import static org.junit.jupiter.api.Assertions.assertEquals; 25 | import static org.mockito.Mockito.any; 26 | import static org.mockito.Mockito.mock; 27 | import static org.mockito.Mockito.verify; 28 | import static org.mockito.Mockito.when; 29 | 30 | @SuppressWarnings("unused") 31 | @DisplayName("Detailed \"connect\" command cases") 32 | class ConnectCommandTest extends BaseStfCommanderTest { 33 | 34 | private static final int MOCK_OBJECT_COUNT = 4; 35 | private List cachedDevices = new ArrayList<>(); 36 | private ArgumentCaptor paramsCaptor = ArgumentCaptor.forClass(DevicesParams.class); 37 | private ConnectCommand connectCommand; 38 | 39 | @BeforeEach 40 | @Override 41 | protected void setUp() throws IOException { 42 | super.setUp(); 43 | when(cache.getCachedFiles()).thenReturn(cachedDevices); 44 | when(farmClient.connectToDevices(any(DevicesParams.class))).thenReturn(Flowable.just((Notification.createOnNext("url")))); 45 | connectCommand = new ConnectCommand(farmClient, adbRunner, cache, logger); 46 | } 47 | 48 | @DisplayName("Valid index leads to DeviceParams instance creation") 49 | @ParameterizedTest(name = "\"connect -l {0}\" creates DeviceParams with serial {1}") 50 | @MethodSource("validIndexArrayProvider") 51 | void connectToEachCacheItem(int parameter, String targetSerial) throws IOException { 52 | prepareMocks(); 53 | connectCommand.setDevicesIndexesFromCache(Collections.singletonList(parameter)); 54 | connectCommand.execute().test().assertComplete(); 55 | verify(farmClient).connectToDevices(paramsCaptor.capture()); 56 | assertDeviceInParams(targetSerial); 57 | } 58 | 59 | @DisplayName("Invalid index in cache handled with exception") 60 | @ParameterizedTest(name = "\"connect -l {0}\" command should call errorHandler with the InvalidCacheIndexException with index {1}") 61 | @MethodSource("invalidIndexArrayProvider") 62 | void exceptionIsPropagatedIfSomeIndexInvalid(List arrayWithInvalidIndex, int invalidIndex) throws IOException { 63 | prepareMocks(); 64 | connectCommand.setDevicesIndexesFromCache(arrayWithInvalidIndex); 65 | connectCommand.execute().test() 66 | .assertError(throwable -> ((InvalidCacheIndexException) throwable).getIndex() == invalidIndex); 67 | } 68 | 69 | private static Stream invalidIndexArrayProvider() { 70 | return Stream.of(Arguments.of(Arrays.asList(0, 1, 2), 0), Arguments.of(Arrays.asList(1, 2, 7), 7)); 71 | } 72 | 73 | private static Stream validIndexArrayProvider() { 74 | return Stream.of( 75 | Arguments.of(1, "serial_1"), 76 | Arguments.of(2, "serial_2"), 77 | Arguments.of(3, "serial_3"), 78 | Arguments.of(4, "serial_4")); 79 | } 80 | 81 | @SuppressWarnings("SameParameterValue") 82 | private void assertDeviceInParams(String serial) { 83 | assertEquals(serial, paramsCaptor.getValue().getSerialFilterDescription().getTemplates().get(0)); 84 | } 85 | 86 | private void prepareMocks() { 87 | for (int i = 1; i <= MOCK_OBJECT_COUNT; i++) { 88 | final Device mock = mock(Device.class); 89 | when(mock.getSerial()).thenReturn("serial_" + i); 90 | cachedDevices.add(mock); 91 | } 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | GROUP_ID=com.github.e13mort -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/e13mort/stf-console-client/701b99eda601bc9234508e2d0f5e7c3ae1574573/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jan 24 00:57:53 MSK 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /junit.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'org.junit.platform.gradle.plugin' 2 | junitPlatform { 3 | filters { 4 | engines { 5 | include 'junit-jupiter' 6 | } 7 | tags { 8 | if (!project.hasProperty('run-integration')) exclude 'integration' 9 | } 10 | } 11 | enableStandardTestTask true 12 | } 13 | dependencies { 14 | testCompile 'org.junit.jupiter:junit-jupiter-api:5.0.0-RC3' 15 | testCompile 'org.junit.jupiter:junit-jupiter-params:5.0.0-RC3' 16 | testRuntime 'org.junit.jupiter:junit-jupiter-engine:5.0.0-RC3' 17 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':client' -------------------------------------------------------------------------------- /stf_usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/e13mort/stf-console-client/701b99eda601bc9234508e2d0f5e7c3ae1574573/stf_usage.png --------------------------------------------------------------------------------