├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── CHANGELOG.md ├── LICENSE ├── README.md ├── appveyor.yml ├── doc └── Decision Records │ └── 1 Changelog.md ├── findbugs-exclude.xml ├── mvnw ├── mvnw.cmd ├── pom.xml ├── scripts ├── mvn_in_docker.sh └── test.sh └── src ├── main └── java │ └── com │ └── github │ └── stefanbirkner │ └── systemlambda │ ├── Statement.java │ └── SystemLambda.java └── test └── java └── com └── github └── stefanbirkner └── systemlambda ├── AssertNothingWrittenToSystemErrTest.java ├── AssertNothingWrittenToSystemOutTest.java ├── CallableMock.java ├── CatchSystemExitTest.java ├── MethodUnderTest.java ├── MuteSystemErrTest.java ├── MuteSystemOutTest.java ├── RestoreSystemErrChecks.java ├── RestoreSystemOutChecks.java ├── RestoreSystemPropertiesTest.java ├── SecurityManagerMock.java ├── StatementMock.java ├── TapSystemErrAndOutNormalizedTest.java ├── TapSystemErrAndOutTest.java ├── TapSystemErrNormalizedTest.java ├── TapSystemErrTest.java ├── TapSystemOutNormalizedTest.java ├── TapSystemOutTest.java ├── WithEnvironmentVariableTest.java ├── WithSecurityManagerTest.java └── WithTextFromSystemInTest.java /.editorconfig: -------------------------------------------------------------------------------- 1 | # Configuration file for EditorConfig: http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_style = tab 8 | insert_final_newline = true 9 | tab_width = 4 10 | trim_trailing_whitespace = true 11 | 12 | [*.{sh,xml,yml}] 13 | indent_size = 2 14 | indent_style = space 15 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI with different Java versions 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-18.04 9 | 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | - name: Build and Test 14 | run: scripts/test.sh 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .flattened-pom.xml 2 | target 3 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | import java.net.*; 17 | import java.io.*; 18 | import java.nio.channels.*; 19 | import java.util.Properties; 20 | 21 | public class MavenWrapperDownloader { 22 | 23 | private static final String WRAPPER_VERSION = "0.5.6"; 24 | /** 25 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 26 | */ 27 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 28 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 29 | 30 | /** 31 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 32 | * use instead of the default one. 33 | */ 34 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 35 | ".mvn/wrapper/maven-wrapper.properties"; 36 | 37 | /** 38 | * Path where the maven-wrapper.jar will be saved to. 39 | */ 40 | private static final String MAVEN_WRAPPER_JAR_PATH = 41 | ".mvn/wrapper/maven-wrapper.jar"; 42 | 43 | /** 44 | * Name of the property which should be used to override the default download url for the wrapper. 45 | */ 46 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 47 | 48 | public static void main(String args[]) { 49 | System.out.println("- Downloader started"); 50 | File baseDirectory = new File(args[0]); 51 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 52 | 53 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 54 | // wrapperUrl parameter. 55 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 56 | String url = DEFAULT_DOWNLOAD_URL; 57 | if(mavenWrapperPropertyFile.exists()) { 58 | FileInputStream mavenWrapperPropertyFileInputStream = null; 59 | try { 60 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 61 | Properties mavenWrapperProperties = new Properties(); 62 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 63 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 64 | } catch (IOException e) { 65 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 66 | } finally { 67 | try { 68 | if(mavenWrapperPropertyFileInputStream != null) { 69 | mavenWrapperPropertyFileInputStream.close(); 70 | } 71 | } catch (IOException e) { 72 | // Ignore ... 73 | } 74 | } 75 | } 76 | System.out.println("- Downloading from: " + url); 77 | 78 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 79 | if(!outputFile.getParentFile().exists()) { 80 | if(!outputFile.getParentFile().mkdirs()) { 81 | System.out.println( 82 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 83 | } 84 | } 85 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 86 | try { 87 | downloadFileFromURL(url, outputFile); 88 | System.out.println("Done"); 89 | System.exit(0); 90 | } catch (Throwable e) { 91 | System.out.println("- Error downloading"); 92 | e.printStackTrace(); 93 | System.exit(1); 94 | } 95 | } 96 | 97 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 98 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 99 | String username = System.getenv("MVNW_USERNAME"); 100 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 101 | Authenticator.setDefault(new Authenticator() { 102 | @Override 103 | protected PasswordAuthentication getPasswordAuthentication() { 104 | return new PasswordAuthentication(username, password); 105 | } 106 | }); 107 | } 108 | URL website = new URL(urlString); 109 | ReadableByteChannel rbc; 110 | rbc = Channels.newChannel(website.openStream()); 111 | FileOutputStream fos = new FileOutputStream(destination); 112 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 113 | fos.close(); 114 | rbc.close(); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanbirkner/system-lambda/18e1f1ed60d1b4346371cdf09ad21c172f438309/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | This project adheres to 6 | [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | 9 | ## 1.2.1 – 2021-12-28 10 | 11 | Replace `assertThrownBy` with `assertThrows` in documentation. 12 | 13 | `assertThrows` is the correct method name of JUnit Jupiter's assertion for 14 | exceptions. All examples use JUnit Jupiter for assertions. 15 | 16 | 17 | ## 1.2.0 – 2021-01-17 18 | 19 | Add methods for tapping `System.err` and `System.out` simultaneously. 20 | 21 | The output of command-line applications is usually the output to the standard 22 | output stream and the error output stream intermixed. The new methods allow 23 | capturing the intermixed output. 24 | 25 | @Test 26 | void application_writes_text_to_System_err_and_out( 27 | ) throws Exception { 28 | String text = tapSystemErrAndOut(() -> { 29 | System.err.print("text from err"); 30 | System.out.print("text from out"); 31 | }); 32 | assertEquals("text from errtext from out", text); 33 | } 34 | 35 | @Test 36 | void application_writes_mutliple_lines_to_System_err_and_out( 37 | ) throws Exception { 38 | String text = tapSystemErrAndOutNormalized(() -> { 39 | System.err.println("text from err"); 40 | System.out.println("text from out"); 41 | }); 42 | assertEquals("text from err\ntext from out\n", text); 43 | } 44 | 45 | 46 | ## 1.1.1 – 2020-10-12 47 | 48 | Fix examples for tapping `System.err` and `System.out`. 49 | 50 | 51 | ## 1.1.0 – 2020-08-17 52 | 53 | Support Callable for `withEnvironmentVariable`. 54 | 55 | This allows to write more precise and readable tests. The assertion can be 56 | outside the `withEnvironmentVariable` statement and `execute` can be called with 57 | a one-line Lambda expression. E.g. 58 | 59 | @Test 60 | void execute_code_with_environment_variables( 61 | ) throws Exception { 62 | String value = withEnvironmentVariable("key", "the value") 63 | .execute(() -> System.getenv("key")); 64 | assertEquals("the value", value); 65 | } 66 | 67 | 68 | ## 1.0.0 – 2020-05-17 69 | 70 | Initial release with the methods 71 | 72 | - assertNothingWrittenToSystemErr 73 | - assertNothingWrittenToSystemOut 74 | - catchSystemExit 75 | - muteSystemErr 76 | - muteSystemOut 77 | - restoreSystemProperties 78 | - tapSystemErr 79 | - tapSystemErrNormalized 80 | - tapSystemOut 81 | - tapSystemOutNormalized 82 | - withEnvironmentVariable 83 | - withSecurityManager 84 | - withTextFromSystemIn 85 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Stefan Birkner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # System Lambda 2 | 3 | ![Build Status Linux](https://github.com/stefanbirkner/system-lambda/workflows/Java%20CI%20with%20Maven/badge.svg?branch=master) [![Build Status Windows](https://ci.appveyor.com/api/projects/status/4ck6g0triwhvk9dy?svg=true)](https://ci.appveyor.com/project/stefanbirkner/system-lambda) 4 | 5 | System Lambda is a collection of functions for testing code which uses 6 | `java.lang.System`. 7 | 8 | System Lambda is published under the 9 | [MIT license](http://opensource.org/licenses/MIT). It requires at least Java 8. 10 | 11 | For JUnit 4 there is an alternative to System Lambda. Its name is 12 | [System Rules](http://stefanbirkner.github.io/system-rules/index.html). 13 | 14 | ## Installation 15 | 16 | System Lambda is available from 17 | [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.github.stefanbirkner%22%20AND%20a%3A%22system-lambda%22). 18 | 19 | ```xml 20 | 21 | com.github.stefanbirkner 22 | system-lambda 23 | 1.2.1 24 | 25 | ``` 26 | 27 | Please don't forget to add the scope `test` if you're using System Lambda for 28 | tests only. The [changelog](CHANGELOG.md) lists all the changes of System 29 | Lambda. 30 | 31 | 32 | ## Usage 33 | 34 | Import System Lambda's functions by adding 35 | 36 | ```java 37 | import static com.github.stefanbirkner.systemlambda.SystemLambda.*; 38 | ``` 39 | 40 | to your tests. 41 | 42 | 43 | ### System.exit 44 | 45 | Command-line applications terminate by calling `System.exit` with some status 46 | code. If you test such an application then the JVM that executes the test exits 47 | when the application under test calls `System.exit`. You can avoid this with 48 | the method `catchSystemExit` which also returns the status code of the 49 | `System.exit` call. 50 | 51 | ```java 52 | @Test 53 | void application_exits_with_status_42( 54 | ) throws Exception { 55 | int statusCode = catchSystemExit(() -> { 56 | System.exit(42); 57 | }); 58 | assertEquals(42, statusCode); 59 | } 60 | ``` 61 | 62 | The method `catchSystemExit` throws an `AssertionError` if the code under test 63 | does not call `System.exit`. Therefore your test fails with the failure message 64 | "System.exit has not been called." 65 | 66 | 67 | ### Environment Variables 68 | 69 | The method `withEnvironmentVariable` allows you to set environment variables 70 | within your test code that are removed after your code under test is executed. 71 | 72 | ```java 73 | @Test 74 | void execute_code_with_environment_variables( 75 | ) throws Exception { 76 | List values = withEnvironmentVariable("first", "first value") 77 | .and("second", "second value") 78 | .execute(() -> asList( 79 | System.getenv("first"), 80 | System.getenv("second") 81 | )); 82 | assertEquals( 83 | asList("first value", "second value"), 84 | values 85 | ); 86 | } 87 | ``` 88 | 89 | 90 | ### System Properties 91 | 92 | The method `restoreSystemProperties` guarantees that after executing the test 93 | code each System property has the same value like before. Therefore you can 94 | modify System properties inside of the test code without having an impact on 95 | other tests. 96 | 97 | ```java 98 | @Test 99 | void execute_code_that_manipulates_system_properties( 100 | ) throws Exception { 101 | restoreSystemProperties(() -> { 102 | System.setProperty("some.property", "some value"); 103 | //code under test that reads properties (e.g. "some.property") or 104 | //modifies them. 105 | }); 106 | 107 | //Here the value of "some.property" is the same like before. 108 | //E.g. it is not set. 109 | } 110 | ``` 111 | 112 | 113 | ### System.out and System.err 114 | 115 | Command-line applications usually write to the console. If you write such 116 | applications you need to test the output of these applications. The methods 117 | `tapSystemErr`, `tapSystemErrNormalized`, `tapSystemOut`, 118 | `tapSystemOutNormalized`, `tapSystemErrAndOut` and 119 | `tapSystemErrAndOutNormalized` allow you to tap the text that is written to 120 | `System.err`/`System.out`. The methods with the suffix `Normalized` normalize 121 | line breaks to `\n` so that you can run tests with the same assertions on 122 | different operating systems. 123 | 124 | ```java 125 | @Test 126 | void application_writes_text_to_System_err( 127 | ) throws Exception { 128 | String text = tapSystemErr(() -> { 129 | System.err.print("some text"); 130 | }); 131 | assertEquals("some text", text); 132 | } 133 | 134 | @Test 135 | void application_writes_mutliple_lines_to_System_err( 136 | ) throws Exception { 137 | String text = tapSystemErrNormalized(() -> { 138 | System.err.println("first line"); 139 | System.err.println("second line"); 140 | }); 141 | assertEquals("first line\nsecond line\n", text); 142 | } 143 | 144 | @Test 145 | void application_writes_text_to_System_out( 146 | ) throws Exception { 147 | String text = tapSystemOut(() -> { 148 | System.out.print("some text"); 149 | }); 150 | assertEquals("some text", text); 151 | } 152 | 153 | @Test 154 | void application_writes_mutliple_lines_to_System_out( 155 | ) throws Exception { 156 | String text = tapSystemOutNormalized(() -> { 157 | System.out.println("first line"); 158 | System.out.println("second line"); 159 | }); 160 | assertEquals("first line\nsecond line\n", text); 161 | } 162 | 163 | @Test 164 | void application_writes_text_to_System_err_and_out( 165 | ) throws Exception { 166 | String text = tapSystemErrAndOut(() -> { 167 | System.err.print("text from err"); 168 | System.out.print("text from out"); 169 | }); 170 | assertEquals("text from errtext from out", text); 171 | } 172 | 173 | @Test 174 | void application_writes_mutliple_lines_to_System_err_and_out( 175 | ) throws Exception { 176 | String text = tapSystemErrAndOutNormalized(() -> { 177 | System.err.println("text from err"); 178 | System.out.println("text from out"); 179 | }); 180 | assertEquals("text from err\ntext from out\n", text); 181 | } 182 | ``` 183 | 184 | You can assert that nothing is written to `System.err`/`System.out` by wrapping 185 | code with the function 186 | `assertNothingWrittenToSystemErr`/`assertNothingWrittenToSystemOut`. E.g. the 187 | following tests fail: 188 | 189 | ```java 190 | @Test 191 | void fails_because_something_is_written_to_System_err( 192 | ) throws Exception { 193 | assertNothingWrittenToSystemErr(() -> { 194 | System.err.println("some text"); 195 | }); 196 | } 197 | 198 | @Test 199 | void fails_because_something_is_written_to_System_out( 200 | ) throws Exception { 201 | assertNothingWrittenToSystemOut(() -> { 202 | System.out.println("some text"); 203 | }); 204 | } 205 | ``` 206 | 207 | If the code under test writes text to `System.err`/`System.out` then it is 208 | intermixed with the output of your build tool. Therefore you may want to avoid 209 | that the code under test writes to `System.err`/`System.out`. You can achieve 210 | this with the function `muteSystemErr`/`muteSystemOut`. E.g. the following tests 211 | don't write anything to `System.err`/`System.out`: 212 | 213 | ```java 214 | @Test 215 | void nothing_is_written_to_System_err( 216 | ) throws Exception { 217 | muteSystemErr(() -> { 218 | System.err.println("some text"); 219 | }); 220 | } 221 | 222 | @Test 223 | void nothing_is_written_to_System_out( 224 | ) throws Exception { 225 | muteSystemOut(() -> { 226 | System.out.println("some text"); 227 | }); 228 | } 229 | ``` 230 | 231 | ### System.in 232 | 233 | Interactive command-line applications read from `System.in`. If you write such 234 | applications you need to provide input to these applications. You can specify 235 | the lines that are available from `System.in` with the method 236 | `withTextFromSystemIn` 237 | 238 | ```java 239 | @Test 240 | void Scanner_reads_text_from_System_in( 241 | ) throws Exception { 242 | withTextFromSystemIn("first line", "second line") 243 | .execute(() -> { 244 | Scanner scanner = new Scanner(System.in); 245 | scanner.nextLine(); 246 | assertEquals("second line", scanner.nextLine()); 247 | }); 248 | } 249 | ``` 250 | 251 | For a complete test coverage you may also want to simulate `System.in` throwing 252 | exceptions when the application reads from it. You can specify such an 253 | exception (either `RuntimeException` or `IOException`) after specifying the 254 | text. The exception will be thrown by the next `read` after the text has been 255 | consumed. 256 | 257 | ```java 258 | @Test 259 | void System_in_throws_IOException( 260 | ) throws Exception { 261 | withTextFromSystemIn("first line", "second line") 262 | .andExceptionThrownOnInputEnd(new IOException()) 263 | .execute(() -> { 264 | Scanner scanner = new Scanner(System.in); 265 | scanner.nextLine(); 266 | scanner.nextLine(); 267 | assertThrows( 268 | IOException.class, 269 | () -> scanner.readLine() 270 | ); 271 | }); 272 | } 273 | 274 | @Test 275 | void System_in_throws_RuntimeException( 276 | ) throws Exception { 277 | withTextFromSystemIn("first line", "second line") 278 | .andExceptionThrownOnInputEnd(new RuntimeException()) 279 | .execute(() -> { 280 | Scanner scanner = new Scanner(System.in); 281 | scanner.nextLine(); 282 | scanner.nextLine(); 283 | assertThrows( 284 | RuntimeException.class, 285 | () -> scanner.readLine() 286 | ); 287 | }); 288 | } 289 | ``` 290 | 291 | You can write a test that throws an exception immediately by not providing any 292 | text. 293 | 294 | ```java 295 | withTextFromSystemIn() 296 | .andExceptionThrownOnInputEnd(...) 297 | .execute(() -> { 298 | Scanner scanner = new Scanner(System.in); 299 | assertThrows( 300 | ..., 301 | () -> scanner.readLine() 302 | ); 303 | }); 304 | ``` 305 | 306 | ### Security Manager 307 | 308 | The function `withSecurityManager` lets you specify the `SecurityManager` that 309 | is returned by `System.getSecurityManger()` while your code under test is 310 | executed. 311 | 312 | ```java 313 | @Test 314 | void execute_code_with_specific_SecurityManager( 315 | ) throws Exception { 316 | SecurityManager securityManager = new ASecurityManager(); 317 | withSecurityManager( 318 | securityManager, 319 | () -> { 320 | //code under test 321 | //e.g. the following assertion is met 322 | assertSame( 323 | securityManager, 324 | System.getSecurityManager() 325 | ); 326 | } 327 | ); 328 | } 329 | ``` 330 | 331 | After `withSecurityManager(...)` is executed`System.getSecurityManager()` 332 | returns the original security manager again. 333 | 334 | 335 | ## Contributing 336 | 337 | You have three options if you have a feature request, found a bug or 338 | simply have a question about System Lambda. 339 | 340 | * [Write an issue.](https://github.com/stefanbirkner/system-lambda/issues/new) 341 | * Create a pull request. (See [Understanding the GitHub Flow](https://guides.github.com/introduction/flow/index.html)) 342 | * [Write a mail to mail@stefan-birkner.de](mailto:mail@stefan-birkner.de) 343 | 344 | 345 | ## Development Guide 346 | 347 | System Lambda is built with [Maven](http://maven.apache.org/) and must be 348 | compiled under JDK 8. If you want to contribute code then 349 | 350 | * Please write a test for your change. 351 | * Ensure that you didn't break the build by running `mvnw clean verify -Dgpg.skip`. 352 | * Fork the repo and create a pull request. (See [Understanding the GitHub Flow](https://guides.github.com/introduction/flow/index.html)) 353 | 354 | The basic coding style is described in the 355 | [EditorConfig](http://editorconfig.org/) file `.editorconfig`. 356 | 357 | System Lambda supports [GitHub Actions](https://help.github.com/en/actions) 358 | (Linux) and [AppVeyor](http://www.appveyor.com/) (Windows) for continuous 359 | integration. Each pull request is automatically built by both CI servers. 360 | GitHub Actions tests with several Java versions (see Enhanced Testing) while 361 | AppVeyor tests with Java 8 only. 362 | 363 | ### Project Decisions 364 | 365 | There are decision records for some decisions that had been made for System 366 | Lambda. They are stored in the folder 367 | [doc/Decision Records](doc/Decision%20Records) 368 | 369 | ### Build with Docker 370 | 371 | The script 372 | 373 | ./scripts/mvn_in_docker.sh clean verify -Dgpg.skip 374 | 375 | builds System Lambda inside a Docker container. It uses your Maven local 376 | repository. This is helpful if you have a Linux or macOS without JDK 8. 377 | 378 | ### Enhanced Testing 379 | 380 | System Lambda is built with Java 8 and relies on JDK internals that are not 381 | available from Java 16 on (unless you run Java with additional flags). We verify 382 | that System Lambda works with newer Java versions by building it with OpenJDK 8 383 | and executing tests with newer versions of OpenJDK. All this work is put into a 384 | script that you can run with 385 | 386 | ./scripts/test.sh 387 | 388 | The script uses Docker for running the tests. 389 | 390 | 391 | ## Release Guide 392 | 393 | * Select a new version according to the 394 | [Semantic Versioning 2.0.0 Standard](http://semver.org/). 395 | * Update `Changelog.md`. 396 | * Set the new version in `pom.xml` and in the `Installation` section of 397 | this readme. 398 | * Commit the modified `Changelog.md`, `pom.xml` and `README.md`. 399 | * Run `mvnw clean deploy` with JDK 8. 400 | * Add a tag for the release: `git tag system-lambda-X.X.X` 401 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | os: Windows Server 2012 3 | build_script: 4 | - mvnw clean package -DskipTest -Dgpg.skip 5 | test_script: 6 | - mvnw clean verify -Dgpg.skip 7 | cache: 8 | - C:\Users\appveyor\.m2 9 | -------------------------------------------------------------------------------- /doc/Decision Records/1 Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Status: Accepted 4 | 5 | 6 | ## Context 7 | 8 | Sometimes I (Stefan) publish a new release of System Lambda. 9 | 10 | Users find out about new releases, e.g. because I tweet about it or their 11 | dependency management tool tells them about a new version. They want to know 12 | what changed in the new version (or maybe several versions). Some are curious 13 | about new features or bugfixes. Others need this information in order to decide 14 | whether they change their software to use a newer version of System Lambda or 15 | not. 16 | 17 | 18 | ## Decision 19 | 20 | I provide a changelog that is stored in the file CHANGELOG.md. 21 | 22 | A curated list of the changes is the fastest way for users to find out about 23 | changes in System Lambda. 24 | 25 | CHANGELOG.md is a common filename for changelogs. Users who are used to this 26 | convention may look at this file without further guideline. For other users 27 | there is a link to the changelog in the readme file. 28 | 29 | The file is a text file that uses Markdown, because it provides lightweight text 30 | formatting that is nicely rendered by some viewers (e.g. GitHub which hosts 31 | this project) while it still can be viewed and edited by standard text editors. 32 | 33 | 34 | ## Consequences 35 | 36 | Developers and project stakeholders can see the changes without viewing the git 37 | log. 38 | 39 | The changelog must be updated before a new release is published. 40 | 41 | Information may be duplicated between docs, changelog and git log. 42 | -------------------------------------------------------------------------------- /findbugs-exclude.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | fi 118 | 119 | if [ -z "$JAVA_HOME" ]; then 120 | javaExecutable="`which javac`" 121 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 122 | # readlink(1) is not available as standard on Solaris 10. 123 | readLink=`which readlink` 124 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 125 | if $darwin ; then 126 | javaHome="`dirname \"$javaExecutable\"`" 127 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 128 | else 129 | javaExecutable="`readlink -f \"$javaExecutable\"`" 130 | fi 131 | javaHome="`dirname \"$javaExecutable\"`" 132 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 133 | JAVA_HOME="$javaHome" 134 | export JAVA_HOME 135 | fi 136 | fi 137 | fi 138 | 139 | if [ -z "$JAVACMD" ] ; then 140 | if [ -n "$JAVA_HOME" ] ; then 141 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 142 | # IBM's JDK on AIX uses strange locations for the executables 143 | JAVACMD="$JAVA_HOME/jre/sh/java" 144 | else 145 | JAVACMD="$JAVA_HOME/bin/java" 146 | fi 147 | else 148 | JAVACMD="`which java`" 149 | fi 150 | fi 151 | 152 | if [ ! -x "$JAVACMD" ] ; then 153 | echo "Error: JAVA_HOME is not defined correctly." >&2 154 | echo " We cannot execute $JAVACMD" >&2 155 | exit 1 156 | fi 157 | 158 | if [ -z "$JAVA_HOME" ] ; then 159 | echo "Warning: JAVA_HOME environment variable is not set." 160 | fi 161 | 162 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 163 | 164 | # traverses directory structure from process work directory to filesystem root 165 | # first directory with .mvn subdirectory is considered project base directory 166 | find_maven_basedir() { 167 | 168 | if [ -z "$1" ] 169 | then 170 | echo "Path not specified to find_maven_basedir" 171 | return 1 172 | fi 173 | 174 | basedir="$1" 175 | wdir="$1" 176 | while [ "$wdir" != '/' ] ; do 177 | if [ -d "$wdir"/.mvn ] ; then 178 | basedir=$wdir 179 | break 180 | fi 181 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 182 | if [ -d "${wdir}" ]; then 183 | wdir=`cd "$wdir/.."; pwd` 184 | fi 185 | # end of workaround 186 | done 187 | echo "${basedir}" 188 | } 189 | 190 | # concatenates all lines of a file 191 | concat_lines() { 192 | if [ -f "$1" ]; then 193 | echo "$(tr -s '\n' ' ' < "$1")" 194 | fi 195 | } 196 | 197 | BASE_DIR=`find_maven_basedir "$(pwd)"` 198 | if [ -z "$BASE_DIR" ]; then 199 | exit 1; 200 | fi 201 | 202 | ########################################################################################## 203 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 204 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 205 | ########################################################################################## 206 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 207 | if [ "$MVNW_VERBOSE" = true ]; then 208 | echo "Found .mvn/wrapper/maven-wrapper.jar" 209 | fi 210 | else 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 213 | fi 214 | if [ -n "$MVNW_REPOURL" ]; then 215 | jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 216 | else 217 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 218 | fi 219 | while IFS="=" read key value; do 220 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 221 | esac 222 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 223 | if [ "$MVNW_VERBOSE" = true ]; then 224 | echo "Downloading from: $jarUrl" 225 | fi 226 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 227 | if $cygwin; then 228 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 229 | fi 230 | 231 | if command -v wget > /dev/null; then 232 | if [ "$MVNW_VERBOSE" = true ]; then 233 | echo "Found wget ... using wget" 234 | fi 235 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 236 | wget "$jarUrl" -O "$wrapperJarPath" 237 | else 238 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" 239 | fi 240 | elif command -v curl > /dev/null; then 241 | if [ "$MVNW_VERBOSE" = true ]; then 242 | echo "Found curl ... using curl" 243 | fi 244 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 245 | curl -o "$wrapperJarPath" "$jarUrl" -f 246 | else 247 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 248 | fi 249 | 250 | else 251 | if [ "$MVNW_VERBOSE" = true ]; then 252 | echo "Falling back to using Java to download" 253 | fi 254 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 255 | # For Cygwin, switch paths to Windows format before running javac 256 | if $cygwin; then 257 | javaClass=`cygpath --path --windows "$javaClass"` 258 | fi 259 | if [ -e "$javaClass" ]; then 260 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 261 | if [ "$MVNW_VERBOSE" = true ]; then 262 | echo " - Compiling MavenWrapperDownloader.java ..." 263 | fi 264 | # Compiling the Java class 265 | ("$JAVA_HOME/bin/javac" "$javaClass") 266 | fi 267 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 268 | # Running the downloader 269 | if [ "$MVNW_VERBOSE" = true ]; then 270 | echo " - Running MavenWrapperDownloader.java ..." 271 | fi 272 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 273 | fi 274 | fi 275 | fi 276 | fi 277 | ########################################################################################## 278 | # End of extension 279 | ########################################################################################## 280 | 281 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 282 | if [ "$MVNW_VERBOSE" = true ]; then 283 | echo $MAVEN_PROJECTBASEDIR 284 | fi 285 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 286 | 287 | # For Cygwin, switch paths to Windows format before running java 288 | if $cygwin; then 289 | [ -n "$M2_HOME" ] && 290 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 291 | [ -n "$JAVA_HOME" ] && 292 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 293 | [ -n "$CLASSPATH" ] && 294 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 295 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 296 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 297 | fi 298 | 299 | # Provide a "standardized" way to retrieve the CLI args that will 300 | # work with both Windows and non-Windows executions. 301 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 302 | export MAVEN_CMD_LINE_ARGS 303 | 304 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 305 | 306 | exec "$JAVACMD" \ 307 | $MAVEN_OPTS \ 308 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 309 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 310 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 311 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 124 | 125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | com.github.stefanbirkner 7 | lib-parent 8 | 16 9 | 10 | 11 | system-lambda 12 | 1.2.1 13 | jar 14 | 15 | System Lambda 16 | A collection of functions for testing code which uses java.lang.System. 17 | https://github.com/stefanbirkner/system-lambda/ 18 | 2020 19 | 20 | 21 | MIT License 22 | http://opensource.org/licenses/MIT 23 | repo 24 | 25 | 26 | 27 | 28 | scm:git:git://github.com/stefanbirkner/system-lambda.git 29 | scm:git:git@github.com:stefanbirkner/system-lambda.git 30 | https://github.com/stefanbirkner/system-lambda/ 31 | 32 | 33 | 34 | findbugs-exclude.xml 35 | 1.8 36 | 1.8 37 | 38 | 39 | 40 | 41 | com.github.stefanbirkner 42 | fishbowl 43 | 1.4.1 44 | test 45 | 46 | 47 | org.apache.commons 48 | commons-lang3 49 | 3.10 50 | test 51 | 52 | 53 | org.assertj 54 | assertj-core 55 | 3.16.1 56 | test 57 | 58 | 59 | org.junit.jupiter 60 | junit-jupiter 61 | 5.6.2 62 | test 63 | 64 | 65 | 66 | 67 | 68 | 69 | maven-surefire-plugin 70 | 3.0.0-M5 71 | 72 | 1 73 | false 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /scripts/mvn_in_docker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Executes Maven inside a Docker container with Java 8. The image has to be 4 | # executed with options, phases and goals just like the original mvn command. 5 | # E.g. instead of "mvn verify" you have to execute 6 | # 7 | # scripts/mvn_in_docker.sh verify 8 | # 9 | # from the project directory. The user's Maven local repository is used by the 10 | # container. 11 | 12 | set -euxo pipefail 13 | 14 | readonly group=`id -g` 15 | readonly user=`id -u` 16 | 17 | mkdir -p ~/.m2 18 | docker run \ 19 | --rm \ 20 | -e MAVEN_CONFIG=/var/maven/.m2 \ 21 | -u "$user:$group" \ 22 | -v "$(pwd)":/usr/src/system-lambda \ 23 | -v ~/.m2:/var/maven/.m2 \ 24 | -w /usr/src/system-lambda \ 25 | maven:3.8.1-openjdk-8-slim \ 26 | mvn -Duser.home=/var/maven "$@" 27 | rm -r '?/' # I don't know why this directory is created. 28 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Verify that System Lambda can be used with different JDKs. 4 | 5 | set -euo pipefail 6 | 7 | readonly group=`id -g` 8 | readonly user=`id -u` 9 | readonly test_dir="target/test_in_docker" 10 | 11 | build_System_Lambda() 12 | { 13 | ./scripts/mvn_in_docker.sh -Dgpg.skip clean install 14 | } 15 | 16 | replace_line() 17 | { 18 | local file=$1 19 | local line_number=$2 20 | local replacement=$3 21 | 22 | sed \ 23 | -i \ 24 | "${line_number}s/.*/${replacement}/" \ 25 | $file 26 | } 27 | 28 | clear_line() 29 | { 30 | local file=$1 31 | local line_number=$2 32 | 33 | replace_line $file $line_number "" 34 | } 35 | 36 | clear_lines() 37 | { 38 | local file=$1 39 | local range=($(echo $2 | tr "-" "\n")) 40 | local start=${range[0]} 41 | local end=${range[1]} 42 | 43 | for line_number in $(seq $start $end) 44 | do 45 | clear_line "$file" "$line_number" 46 | done 47 | } 48 | 49 | delete_line() 50 | { 51 | local file=$1 52 | local line_number=$2 53 | 54 | sed -i "${line_number}d" $file 55 | } 56 | 57 | delete_lines() 58 | { 59 | local file=$1 60 | local range=($(echo $2 | tr "-" "\n")) 61 | local start=${range[0]} 62 | local end=${range[1]} 63 | 64 | for line_number in $(seq $start $end) 65 | do 66 | delete_line "$file" "$start" 67 | done 68 | } 69 | 70 | insert_line() 71 | { 72 | local file=$1 73 | local line_number=$2 74 | local line=$3 75 | 76 | sed -i "${line_number}i\\${line}\\" $file 77 | } 78 | 79 | # The purpose of the test pom.xml is to run the System Lambda tests against a 80 | # System Lambda that was build with Java 8. The Java 8 System Lambda has to be 81 | # published to the local Maven repository. The test pom.xml has a dependency to 82 | # this artifact and also the original test dependencies of System Lambda. 83 | # I decided to build the test pom.xml by copying and modifying System Lambda's 84 | # pom.xml so that I keep System Lambda's test setup. 85 | create_test_pom() 86 | { 87 | local java_version=$1 88 | local test_pom="$test_dir/pom.xml" 89 | 90 | cp pom.xml $test_pom 91 | 92 | # Delete everything after 4.0.0 and before the 93 | # first dependency except groupId, artifactId and version of System Lambda. 94 | delete_line $test_pom 5 # Line with 95 | delete_lines $test_pom "6-9" 96 | delete_lines $test_pom "8-34" 97 | 98 | # Wrap groupId, artifactId and version of System Lambda with 99 | # ... 100 | insert_line $test_pom 5 " " 101 | insert_line $test_pom 9 " " 102 | 103 | # Add pom.xml "header" 104 | insert_line $test_pom 5 " dummy" 105 | insert_line $test_pom 6 " dummy" 106 | insert_line $test_pom 7 " 0-SNAPSHOT" 107 | insert_line $test_pom 8 " jar" 108 | insert_line $test_pom 9 "" 109 | insert_line $test_pom 10 " " 110 | insert_line $test_pom 11 " ${java_version}" 111 | insert_line $test_pom 12 " ${java_version}" 112 | insert_line $test_pom 13 " UTF-8" 113 | insert_line $test_pom 14 " " 114 | insert_line $test_pom 15 "" 115 | insert_line $test_pom 16 " " 116 | } 117 | 118 | # Some methods of SecurityManager had been removed after Java 8. We need to 119 | # delete code that calls or overrides these methods so that we can compile the 120 | # tests with newer JDKs. 121 | remove_code_from_test_classes_that_does_not_work_with_newer_JDKs() 122 | { 123 | local dir_with_classes="$test_dir/src/test/java/com/github/stefanbirkner/systemlambda" 124 | 125 | # Some methods of SecurityManager had been removed after Java 8. We need to 126 | # delete code that calls or overrides these methods so that we can compile 127 | # the tests with newer JDKs. 128 | clear_lines "${dir_with_classes}/CatchSystemExitTest.java" "242-330" 129 | clear_lines "${dir_with_classes}/SecurityManagerMock.java" "158-162" 130 | clear_lines "${dir_with_classes}/SecurityManagerMock.java" "164-168" 131 | clear_lines "${dir_with_classes}/SecurityManagerMock.java" "203-210" 132 | clear_lines "${dir_with_classes}/SecurityManagerMock.java" "224-228" 133 | clear_lines "${dir_with_classes}/SecurityManagerMock.java" "236-241" 134 | } 135 | 136 | copy_test_classes() 137 | { 138 | mkdir -p "$test_dir/src" 139 | cp -R src/test "$test_dir/src/test" 140 | } 141 | 142 | create_test_project() 143 | { 144 | local java_version=$1 145 | 146 | copy_test_classes 147 | remove_code_from_test_classes_that_does_not_work_with_newer_JDKs 148 | create_test_pom $java_version 149 | } 150 | 151 | print_headline() 152 | { 153 | local java_version=$1 154 | set +u 155 | local options="$2" 156 | set -u 157 | 158 | echo "" 159 | echo "Test System Lambda with Java $java_version $options" 160 | echo "" 161 | } 162 | 163 | test_with_JDK() 164 | { 165 | local java_version=$1 166 | set +u 167 | local options="$2" 168 | set -u 169 | 170 | print_headline $java_version $options 171 | create_test_project $java_version 172 | docker run \ 173 | --rm \ 174 | -e MAVEN_CONFIG=/var/maven/.m2 \ 175 | -u "$user:$group" \ 176 | -v "$(pwd)/$test_dir":/usr/src/system-lambda \ 177 | -v ~/.m2:/var/maven/.m2 \ 178 | -w /usr/src/system-lambda \ 179 | "maven:3.8.3-openjdk-$java_version-slim" \ 180 | mvn -Dgpg.skip \ 181 | -D"maven.compiler.source=$java_version" \ 182 | -D"maven.compiler.target=$java_version" \ 183 | -Duser.home=/var/maven \ 184 | clean test $options 185 | } 186 | 187 | # We test with all LTS releases and the latest non-LTS release 188 | build_System_Lambda 189 | test_with_JDK 11 190 | test_with_JDK 17 191 | test_with_JDK 17 "-DargLine=--add-opens=java.base/java.util=ALL-UNNAMED" 192 | -------------------------------------------------------------------------------- /src/main/java/com/github/stefanbirkner/systemlambda/Statement.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | /** 4 | * Code that should be executed by on of the methods of {@link SystemLambda}. 5 | * This code may throw an {@link Exception}. Therefore we cannot use 6 | * {@link Runnable}. 7 | */ 8 | public interface Statement { 9 | /** 10 | * Execute the statement. 11 | * 12 | * @throws Exception the statement may throw an arbitrary exception. 13 | */ 14 | void execute() throws Exception; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/github/stefanbirkner/systemlambda/SystemLambda.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import java.io.*; 4 | import java.lang.reflect.Field; 5 | import java.net.InetAddress; 6 | import java.security.Permission; 7 | import java.util.*; 8 | import java.util.concurrent.Callable; 9 | 10 | import static java.lang.Class.forName; 11 | import static java.lang.System.*; 12 | import static java.nio.charset.Charset.defaultCharset; 13 | import static java.util.Arrays.stream; 14 | import static java.util.Collections.singletonMap; 15 | import static java.util.stream.Collectors.joining; 16 | 17 | /** 18 | * {@code SystemLambda} is a collection of functions for testing code 19 | * that uses {@code java.lang.System}. 20 | * 21 | *

System.exit

22 | *

Command-line applications terminate by calling {@code System.exit} with 23 | * some status code. If you test such an application then the JVM that runs the 24 | * test exits when the application under test calls {@code System.exit}. You can 25 | * avoid this with the method 26 | * {@link #catchSystemExit(Statement) catchSystemExit} which also returns the 27 | * status code of the {@code System.exit} call. 28 | * 29 | *

  30 |  * @Test
  31 |  * void application_exits_with_status_42(
  32 |  * ) throws Exception {
  33 |  *   int statusCode = catchSystemExit((){@literal ->} {
  34 |  *     System.exit(42);
  35 |  *   });
  36 |  *   assertEquals(42, statusCode);
  37 |  * }
  38 |  * 
39 | * 40 | * The method {@code catchSystemExit} throws an {@code AssertionError} if the 41 | * code under test does not call {@code System.exit}. Therefore your test fails 42 | * with the failure message "System.exit has not been called." 43 | * 44 | *

Environment Variables

45 | * 46 | *

The method 47 | * {@link #withEnvironmentVariable(String, String) withEnvironmentVariable} 48 | * allows you to set environment variables within your test code that are 49 | * removed after your code under test is executed. 50 | *

  51 |  * @Test
  52 |  * void execute_code_with_environment_variables(
  53 |  * ) throws Exception {
  54 |  *  {@literal List} values = withEnvironmentVariable("first", "first value")
  55 |  *     .and("second", "second value")
  56 |  *     .execute((){@literal ->} asList(
  57 |  *       System.getenv("first"),
  58 |  *       System.getenv("second")
  59 |  *     ));
  60 |  *   assertEquals(
  61 |  *     asList("first value", "second value"),
  62 |  *     values
  63 |  *   );
  64 |  * }
65 | * 66 | *

System Properties

67 | * 68 | *

The function 69 | * {@link #restoreSystemProperties(Statement) restoreSystemProperties} 70 | * guarantees that after executing the test code each System property has the 71 | * same value like before. Therefore you can modify System properties inside of 72 | * the test code without having an impact on other tests. 73 | *

  74 |  * @Test
  75 |  * void execute_code_that_manipulates_system_properties(
  76 |  * ) throws Exception {
  77 |  *   restoreSystemProperties((){@literal ->} {
  78 |  *     System.setProperty("some.property", "some value");
  79 |  *     //code under test that reads properties (e.g. "some.property") or
  80 |  *     //modifies them.
  81 |  *   });
  82 |  * }
  83 |  * 
84 | * 85 | *

System.out and System.err

86 | *

Command-line applications usually write to the console. If you write such 87 | * applications you need to test the output of these applications. The methods 88 | * {@link #tapSystemErr(Statement) tapSystemErr}, 89 | * {@link #tapSystemErrNormalized(Statement) tapSystemErrNormalized}, 90 | * {@link #tapSystemOut(Statement) tapSystemOut}, 91 | * {@link #tapSystemOutNormalized(Statement) tapSystemOutNormalized}, 92 | * {@link #tapSystemErrAndOut(Statement) tapSystemErrAndOut} and 93 | * {@link #tapSystemErrAndOutNormalized(Statement) tapSystemErrAndOutNormalized} 94 | * allow you 95 | * to tap the text that is written to {@code System.err}/{@code System.out}. The 96 | * methods with the suffix {@code Normalized} normalize line breaks to 97 | * {@code \n} so that you can run tests with the same assertions on different 98 | * operating systems. 99 | * 100 | *

 101 |  * @Test
 102 |  * void application_writes_text_to_System_err(
 103 |  * ) throws Exception {
 104 |  *   String text = tapSystemErr((){@literal ->} {
 105 |  *     System.err.print("some text");
 106 |  *   });
 107 |  *   assertEquals(text, "some text");
 108 |  * }
 109 |  *
 110 |  * @Test
 111 |  * void application_writes_mutliple_lines_to_System_err(
 112 |  * ) throws Exception {
 113 |  *   String text = tapSystemErrNormalized((){@literal ->} {
 114 |  *     System.err.println("first line");
 115 |  *     System.err.println("second line");
 116 |  *   });
 117 |  *   assertEquals(text, "first line\nsecond line\n");
 118 |  * }
 119 |  *
 120 |  * @Test
 121 |  * void application_writes_text_to_System_out(
 122 |  * ) throws Exception {
 123 |  *   String text = tapSystemOut((){@literal ->} {
 124 |  *     System.out.print("some text");
 125 |  *   });
 126 |  *   assertEquals(text, "some text");
 127 |  * }
 128 |  *
 129 |  * @Test
 130 |  * void application_writes_mutliple_lines_to_System_out(
 131 |  * ) throws Exception {
 132 |  *   String text = tapSystemOutNormalized((){@literal ->} {
 133 |  *     System.out.println("first line");
 134 |  *     System.out.println("second line");
 135 |  *   });
 136 |  *   assertEquals(text, "first line\nsecond line\n");
 137 |  * }
 138 |  *
 139 |  * @Test
 140 |  * void application_writes_text_to_System_err_and_out(
 141 |  * ) throws Exception {
 142 |  *   String text = tapSystemErrAndOut((){@literal ->} {
 143 |  *     System.err.print("text from err");
 144 |  *     System.out.print("text from out");
 145 |  *   });
 146 |  *   assertEquals("text from errtext from out", text);
 147 |  * }
 148 |  *
 149 |  * @Test
 150 |  * void application_writes_mutliple_lines_to_System_err_and_out(
 151 |  * ) throws Exception {
 152 |  *   String text = tapSystemErrAndOutNormalized((){@literal ->} {
 153 |  *     System.err.println("text from err");
 154 |  *     System.out.println("text from out");
 155 |  *   });
 156 |  *   assertEquals("text from err\ntext from out\n", text);
 157 |  * }
158 | * 159 | *

You can assert that nothing is written to 160 | * {@code System.err}/{@code System.out} by wrapping code with the function 161 | * {@link #assertNothingWrittenToSystemErr(Statement) 162 | * assertNothingWrittenToSystemErr}/{@link #assertNothingWrittenToSystemOut(Statement) 163 | * assertNothingWrittenToSystemOut}. E.g. the following tests fail: 164 | *

 165 |  * @Test
 166 |  * void fails_because_something_is_written_to_System_err(
 167 |  * ) throws Exception {
 168 |  *   assertNothingWrittenToSystemErr((){@literal ->} {
 169 |  *     System.err.println("some text");
 170 |  *   });
 171 |  * }
 172 |  *
 173 |  * @Test
 174 |  * void fails_because_something_is_written_to_System_out(
 175 |  * ) throws Exception {
 176 |  *   assertNothingWrittenToSystemOut((){@literal ->} {
 177 |  *     System.out.println("some text");
 178 |  *   });
 179 |  * }
 180 |  * 
181 | * 182 | *

If the code under test writes text to 183 | * {@code System.err}/{@code System.out} then it is intermixed with the output 184 | * of your build tool. Therefore you may want to avoid that the code under test 185 | * writes to {@code System.err}/{@code System.out}. You can achieve this with 186 | * the function {@link #muteSystemErr(Statement) 187 | * muteSystemErr}/{@link #muteSystemOut(Statement) muteSystemOut}. E.g. the 188 | * following tests don't write anything to 189 | * {@code System.err}/{@code System.out}: 190 | *

 191 |  * @Test
 192 |  * void nothing_is_written_to_System_err(
 193 |  * ) throws Exception {
 194 |  *   muteSystemErr((){@literal ->} {
 195 |  *     System.err.println("some text");
 196 |  *   });
 197 |  * }
 198 |  *
 199 |  * @Test
 200 |  * void nothing_is_written_to_System_out(
 201 |  * ) throws Exception {
 202 |  *   muteSystemOut((){@literal ->} {
 203 |  *     System.out.println("some text");
 204 |  *   });
 205 |  * }
 206 |  * 
207 | * 208 | *

System.in

209 | * 210 | *

Interactive command-line applications read from {@code System.in}. If you 211 | * write such applications you need to provide input to these applications. You 212 | * can specify the lines that are available from {@code System.in} with the 213 | * method {@link #withTextFromSystemIn(String...) withTextFromSystemIn} 214 | *

 215 |  * @Test
 216 |  * void Scanner_reads_text_from_System_in(
 217 |  * ) throws Exception {
 218 |  *   withTextFromSystemIn("first line", "second line")
 219 |  *     .execute((){@literal ->} {
 220 |  *       Scanner scanner = new Scanner(System.in);
 221 |  *       scanner.nextLine();
 222 |  *       assertEquals("first line", scanner.nextLine());
 223 |  *     });
 224 |  * }
 225 |  * 
226 | * 227 | *

For complete test coverage you may also want to simulate {@code System.in} 228 | * throwing exceptions when the application reads from it. You can specify such 229 | * an exception (either {@code RuntimeException} or {@code IOException}) after 230 | * specifying the text. The exception will be thrown by the next {@code read} 231 | * after the text has been consumed. 232 | *

 233 |  * @Test
 234 |  * void System_in_throws_IOException(
 235 |  * ) throws Exception {
 236 |  *   withTextFromSystemIn("first line", "second line")
 237 |  *     .andExceptionThrownOnInputEnd(new IOException())
 238 |  *     .execute((){@literal ->} {
 239 |  *       Scanner scanner = new Scanner(System.in);
 240 |  *       scanner.nextLine();
 241 |  *       scanner.nextLine();
 242 |  *       assertThrows(
 243 |  *         IOException.class,
 244 |  *         (){@literal ->} scanner.readLine()
 245 |  *       );
 246 |  *   });
 247 |  * }
 248 |  *
 249 |  * @Test
 250 |  * void System_in_throws_RuntimeException(
 251 |  * ) throws Exception {
 252 |  *   withTextFromSystemIn("first line", "second line")
 253 |  *     .andExceptionThrownOnInputEnd(new RuntimeException())
 254 |  *     .execute((){@literal ->} {
 255 |  *       Scanner scanner = new Scanner(System.in);
 256 |  *       scanner.nextLine();
 257 |  *       scanner.nextLine();
 258 |  *       assertThrows(
 259 |  *	        RuntimeException.class,
 260 |  *          (){@literal ->} scanner.readLine()
 261 |  *       );
 262 |  * 	   });
 263 |  * }
 264 |  * 
265 | * 266 | *

You can write a test that throws an exception immediately by not providing 267 | * any text. 268 | *

 269 |  * withTextFromSystemIn()
 270 |  *   .andExceptionThrownOnInputEnd(...)
 271 |  *   .execute((){@literal ->} {
 272 |  *     Scanner scanner = new Scanner(System.in);
 273 |  *     assertThrows(
 274 |  *       ...,
 275 |  *       (){@literal ->} scanner.readLine()
 276 |  *     );
 277 |  *   });
 278 |  * 
279 | * 280 | *

Security Manager

281 | * 282 | *

The function 283 | * {@link #withSecurityManager(SecurityManager, Statement) withSecurityManager} 284 | * lets you specify the {@code SecurityManager} that is returned by 285 | * {@code System.getSecurityManger()} while your code under test is executed. 286 | *

 287 |  * @Test
 288 |  * void execute_code_with_specific_SecurityManager(
 289 |  * ) throws Exception {
 290 |  *   SecurityManager securityManager = new ASecurityManager();
 291 |  *   withSecurityManager(
 292 |  *     securityManager,
 293 |  *     (){@literal ->} {
 294 |  *       //code under test
 295 |  *       //e.g. the following assertion is met
 296 |  *       assertSame(
 297 |  *         securityManager,
 298 |  *         System.getSecurityManager()
 299 |  *       );
 300 |  *     }
 301 |  *   );
 302 |  * }
 303 |  * 
304 | *

After {@code withSecurityManager(...)} is executed 305 | * {@code System.getSecurityManager()} returns the original security manager 306 | * again. 307 | */ 308 | public class SystemLambda { 309 | 310 | private static final boolean AUTO_FLUSH = true; 311 | private static final String DEFAULT_ENCODING = defaultCharset().name(); 312 | 313 | /** 314 | * Executes the statement and fails (throws an {@code AssertionError}) if 315 | * the statement tries to write to {@code System.err}. 316 | *

The following test fails 317 | *

 318 | 	 * @Test
 319 | 	 * void fails_because_something_is_written_to_System_err(
 320 | 	 * ) throws Exception {
 321 | 	 *   assertNothingWrittenToSystemErr((){@literal ->} {
 322 | 	 *     System.err.println("some text");
 323 | 	 *   });
 324 | 	 * }
 325 | 	 * 
326 | * The test fails with the failure "Tried to write 's' to System.err 327 | * although this is not allowed." 328 | * 329 | * @param statement an arbitrary piece of code. 330 | * @throws AssertionError if the statements tries to write to 331 | * {@code System.err}. 332 | * @throws Exception any exception thrown by the statement. 333 | * @see #assertNothingWrittenToSystemOut(Statement) 334 | * @since 1.0.0 335 | */ 336 | public static void assertNothingWrittenToSystemErr( 337 | Statement statement 338 | ) throws Exception { 339 | executeWithSystemErrReplacement( 340 | new DisallowWriteStream(), 341 | statement 342 | ); 343 | } 344 | 345 | /** 346 | * Executes the statement and fails (throws an {@code AssertionError}) if 347 | * the statement tries to write to {@code System.out}. 348 | *

The following test fails 349 | *

 350 | 	 * @Test
 351 | 	 * void fails_because_something_is_written_to_System_out(
 352 | 	 * ) throws Exception {
 353 | 	 *   assertNothingWrittenToSystemOut((){@literal ->} {
 354 | 	 *     System.out.println("some text");
 355 | 	 *   });
 356 | 	 * }
 357 | 	 * 
358 | * The test fails with the failure "Tried to write 's' to System.out 359 | * although this is not allowed." 360 | * 361 | * @param statement an arbitrary piece of code. 362 | * @throws AssertionError if the statements tries to write to 363 | * {@code System.out}. 364 | * @throws Exception any exception thrown by the statement. 365 | * @see #assertNothingWrittenToSystemErr(Statement) 366 | * @since 1.0.0 367 | */ 368 | public static void assertNothingWrittenToSystemOut( 369 | Statement statement 370 | ) throws Exception { 371 | executeWithSystemOutReplacement( 372 | new DisallowWriteStream(), 373 | statement 374 | ); 375 | } 376 | 377 | /** 378 | * Executes the statement and returns the status code that is provided to 379 | * {@code System.exit(int)} within the statement. Additionally it avoids 380 | * that the JVM is shut down because of a call to {@code System.exit(int)}. 381 | *
 382 | 	 *{@literal @Test}
 383 | 	 * void application_exits_with_status_42(
 384 | 	 * ) throws Exception {
 385 | 	 *   int statusCode = catchSystemExit((){@literal ->} {
 386 | 	 *     System.exit(42);
 387 | 	 *   });
 388 | 	 *   assertEquals(42, statusCode);
 389 | 	 * }
 390 | 	 * 
391 | * @param statement an arbitrary piece of code. 392 | * @return the status code provided to {@code System.exit(int)}. 393 | * @throws AssertionError if the statement does not call 394 | * {@code System.exit(int)}. 395 | * @throws Exception any exception thrown by the statement. 396 | * @since 1.0.0 397 | */ 398 | public static int catchSystemExit( 399 | Statement statement 400 | ) throws Exception { 401 | NoExitSecurityManager noExitSecurityManager 402 | = new NoExitSecurityManager(getSecurityManager()); 403 | try { 404 | withSecurityManager(noExitSecurityManager, statement); 405 | } catch (CheckExitCalled ignored) { 406 | } 407 | return checkSystemExit(noExitSecurityManager); 408 | } 409 | 410 | /** 411 | * Executes the statement and suppresses the output of the statement to 412 | * {@code System.err}. Use this to avoid that the output of your build tool 413 | * gets mixed with the output of the code under test. 414 | *
 415 | 	 * @Test
 416 | 	 * void nothing_is_written_to_System_err(
 417 | 	 * ) throws Exception {
 418 | 	 *   muteSystemErr((){@literal ->} {
 419 | 	 *       System.err.println("some text");
 420 | 	 *     }
 421 | 	 *   );
 422 | 	 * }
 423 | 	 * 
424 | * 425 | * @param statement an arbitrary piece of code. 426 | * @throws Exception any exception thrown by the statement. 427 | * @see #muteSystemOut(Statement) 428 | * @since 1.0.0 429 | */ 430 | public static void muteSystemErr( 431 | Statement statement 432 | ) throws Exception { 433 | executeWithSystemErrReplacement( 434 | new NoopStream(), 435 | statement 436 | ); 437 | } 438 | 439 | /** 440 | * Executes the statement and suppresses the output of the statement to 441 | * {@code System.out}. Use this to avoid that the output of your build tool 442 | * gets mixed with the output of the code under test. 443 | *
 444 | 	 * @Test
 445 | 	 * void nothing_is_written_to_System_out(
 446 | 	 * ) throws Exception {
 447 | 	 *   muteSystemOut((){@literal ->} {
 448 | 	 *     System.out.println("some text");
 449 | 	 *   });
 450 | 	 * }
 451 | 	 * 
452 | * 453 | * @param statement an arbitrary piece of code. 454 | * @throws Exception any exception thrown by the statement. 455 | * @see #muteSystemErr(Statement) 456 | * @since 1.0.0 457 | */ 458 | public static void muteSystemOut( 459 | Statement statement 460 | ) throws Exception { 461 | executeWithSystemOutReplacement( 462 | new NoopStream(), 463 | statement 464 | ); 465 | } 466 | 467 | /** 468 | * Executes the statement and restores the system properties after the 469 | * statement has been executed. This allows you to set or clear system 470 | * properties within the statement without affecting other tests. 471 | *
 472 | 	 * @Test
 473 | 	 * void execute_code_that_manipulates_system_properties(
 474 | 	 * ) throws Exception {
 475 | 	 *   System.clearProperty("some property");
 476 | 	 *   System.setProperty("another property", "value before test");
 477 | 	 *
 478 | 	 *   restoreSystemProperties((){@literal ->} {
 479 | 	 *     System.setProperty("some property", "some value");
 480 | 	 *     assertEquals(
 481 | 	 *       "some value",
 482 | 	 *       System.getProperty("some property")
 483 | 	 *     );
 484 | 	 *
 485 | 	 *     System.clearProperty("another property");
 486 | 	 *     assertNull(
 487 | 	 *       System.getProperty("another property")
 488 | 	 *     );
 489 | 	 *   });
 490 | 	 *
 491 | 	 *   //values are restored after test
 492 | 	 *   assertNull(
 493 | 	 *     System.getProperty("some property")
 494 | 	 *   );
 495 | 	 *   assertEquals(
 496 | 	 *     "value before test",
 497 | 	 *     System.getProperty("another property")
 498 | 	 *   );
 499 | 	 * }
 500 | 	 * 
501 | * @param statement an arbitrary piece of code. 502 | * @throws Exception any exception thrown by the statement. 503 | * @since 1.0.0 504 | */ 505 | public static void restoreSystemProperties( 506 | Statement statement 507 | ) throws Exception { 508 | Properties originalProperties = getProperties(); 509 | setProperties(copyOf(originalProperties)); 510 | try { 511 | statement.execute(); 512 | } finally { 513 | setProperties(originalProperties); 514 | } 515 | } 516 | 517 | /** 518 | * Executes the statement and returns the text that was written to 519 | * {@code System.err} by the statement. 520 | *
 521 | 	 * @Test
 522 | 	 * void application_writes_text_to_System_err(
 523 | 	 * ) throws Exception {
 524 | 	 *   String textWrittenToSystemErr = tapSystemErr((){@literal ->} {
 525 | 	 *     System.err.print("some text");
 526 | 	 *   });
 527 | 	 *   assertEquals("some text", textWrittenToSystemErr);
 528 | 	 * }
 529 | 	 * 
530 | * 531 | * @param statement an arbitrary piece of code. 532 | * @return text that is written to {@code System.err} by the statement. 533 | * @throws Exception any exception thrown by the statement. 534 | * @see #tapSystemOut(Statement) 535 | * @see #tapSystemErrAndOut(Statement) 536 | * @see #tapSystemErrAndOutNormalized(Statement) 537 | * @since 1.0.0 538 | */ 539 | public static String tapSystemErr( 540 | Statement statement 541 | ) throws Exception { 542 | TapStream tapStream = new TapStream(); 543 | executeWithSystemErrReplacement( 544 | tapStream, 545 | statement 546 | ); 547 | return tapStream.textThatWasWritten(); 548 | } 549 | 550 | /** 551 | * Executes the statement and returns the text that was written to 552 | * {@code System.err} by the statement. New line characters are replaced 553 | * with a single {@code \n}. 554 | *
 555 | 	 * @Test
 556 | 	 * void application_writes_mutliple_lines_to_System_err(
 557 | 	 * ) throws Exception {
 558 | 	 *   String textWrittenToSystemErr = tapSystemErrNormalized((){@literal ->} {
 559 | 	 *     System.err.println("some text");
 560 | 	 *   });
 561 | 	 *   assertEquals("some text\n", textWrittenToSystemErr);
 562 | 	 * }
 563 | 	 * 
564 | * 565 | * @param statement an arbitrary piece of code. 566 | * @return text that is written to {@code System.err} by the statement. 567 | * @throws Exception any exception thrown by the statement. 568 | * @see #tapSystemOut(Statement) 569 | * @see #tapSystemErrAndOut(Statement) 570 | * @see #tapSystemErrAndOutNormalized(Statement) 571 | * @since 1.0.0 572 | */ 573 | public static String tapSystemErrNormalized( 574 | Statement statement 575 | ) throws Exception { 576 | return tapSystemErr(statement) 577 | .replace(lineSeparator(), "\n"); 578 | } 579 | 580 | /** 581 | * Executes the statement and returns the text that was written to 582 | * {@code System.err} and {@code System.out} by the statement. 583 | *
 584 | 	 * @Test
 585 | 	 * void application_writes_text_to_System_err_and_out(
 586 | 	 * ) throws Exception {
 587 | 	 *   String text = tapSystemErrAndOut((){@literal ->} {
 588 | 	 *     System.err.print("text from err");
 589 | 	 *     System.out.print("text from out");
 590 | 	 *   });
 591 | 	 *   assertEquals("text from errtext from out", text);
 592 | 	 * }
 593 | 	 * 
594 | * 595 | * @param statement an arbitrary piece of code. 596 | * @return text that is written to {@code System.err} and {@code System.out} 597 | * by the statement. 598 | * @throws Exception any exception thrown by the statement. 599 | * @see #tapSystemErrAndOutNormalized(Statement) 600 | * @see #tapSystemErr(Statement) 601 | * @see #tapSystemErrNormalized(Statement) 602 | * @see #tapSystemOut(Statement) 603 | * @see #tapSystemOutNormalized(Statement) 604 | * @since 1.2.0 605 | */ 606 | public static String tapSystemErrAndOut( 607 | Statement statement 608 | ) throws Exception { 609 | TapStream tapStream = new TapStream(); 610 | executeWithSystemErrReplacement( 611 | tapStream, 612 | () -> executeWithSystemOutReplacement( 613 | tapStream, 614 | statement 615 | ) 616 | ); 617 | return tapStream.textThatWasWritten(); 618 | } 619 | 620 | /** 621 | * Executes the statement and returns the text that was written to 622 | * {@code System.err} and {@code System.out} by the statement. New line 623 | * characters are replaced with a single {@code \n}. 624 | *
 625 | 	 * @Test
 626 | 	 * void application_writes_mutliple_lines_to_System_err_and_out(
 627 | 	 * ) throws Exception {
 628 | 	 *   String text = tapSystemErrAndOutNormalized((){@literal ->} {
 629 | 	 *     System.err.println("text from err");
 630 | 	 *     System.out.println("text from out");
 631 | 	 *   });
 632 | 	 *   assertEquals("text from err\ntext from out\n", text);
 633 | 	 * }
 634 | 	 * 
635 | * 636 | * @param statement an arbitrary piece of code. 637 | * @return text that is written to {@code System.err} and {@code System.out} 638 | * by the statement. 639 | * @throws Exception any exception thrown by the statement. 640 | * @see #tapSystemErrAndOut(Statement) 641 | * @see #tapSystemErr(Statement) 642 | * @see #tapSystemErrNormalized(Statement) 643 | * @see #tapSystemOut(Statement) 644 | * @see #tapSystemOutNormalized(Statement) 645 | * @since 1.2.0 646 | */ 647 | public static String tapSystemErrAndOutNormalized( 648 | Statement statement 649 | ) throws Exception { 650 | return tapSystemErrAndOut(statement) 651 | .replace(lineSeparator(), "\n"); 652 | } 653 | 654 | /** 655 | * Executes the statement and returns the text that was written to 656 | * {@code System.out} by the statement. 657 | *
 658 | 	 * @Test
 659 | 	 * void application_writes_text_to_System_out(
 660 | 	 * ) throws Exception {
 661 | 	 *   String textWrittenToSystemOut = tapSystemOut((){@literal ->} {
 662 | 	 *     System.out.print("some text");
 663 | 	 *   });
 664 | 	 *   assertEquals("some text", textWrittenToSystemOut);
 665 | 	 * }
 666 | 	 * 
667 | * 668 | * @param statement an arbitrary piece of code. 669 | * @return text that is written to {@code System.out} by the statement. 670 | * @throws Exception any exception thrown by the statement. 671 | * @see #tapSystemErr(Statement) 672 | * @see #tapSystemErrAndOut(Statement) 673 | * @see #tapSystemErrAndOutNormalized(Statement) 674 | * @since 1.0.0 675 | */ 676 | public static String tapSystemOut( 677 | Statement statement 678 | ) throws Exception { 679 | TapStream tapStream = new TapStream(); 680 | executeWithSystemOutReplacement( 681 | tapStream, 682 | statement 683 | ); 684 | return tapStream.textThatWasWritten(); 685 | } 686 | 687 | /** 688 | * Executes the statement and returns the text that was written to 689 | * {@code System.out} by the statement. New line characters are replaced 690 | * with a single {@code \n}. 691 | *
 692 | 	 * @Test
 693 | 	 * void application_writes_mutliple_lines_to_System_out(
 694 | 	 * ) throws Exception {
 695 | 	 *   String textWrittenToSystemOut = tapSystemOutNormalized((){@literal ->} {
 696 | 	 *     System.out.println("some text");
 697 | 	 *   });
 698 | 	 *   assertEquals("some text\n", textWrittenToSystemOut);
 699 | 	 * }
 700 | 	 * 
701 | * 702 | * @param statement an arbitrary piece of code. 703 | * @return text that is written to {@code System.out} by the statement. 704 | * @throws Exception any exception thrown by the statement. 705 | * @see #tapSystemErr(Statement) 706 | * @see #tapSystemErrAndOut(Statement) 707 | * @see #tapSystemErrAndOutNormalized(Statement) 708 | * @since 1.0.0 709 | */ 710 | public static String tapSystemOutNormalized( 711 | Statement statement 712 | ) throws Exception { 713 | return tapSystemOut(statement) 714 | .replace(lineSeparator(), "\n"); 715 | } 716 | 717 | /** 718 | * Executes the statement with the specified environment variables. All 719 | * changes to environment variables are reverted after the statement has 720 | * been executed. 721 | *
 722 | 	 * @Test
 723 | 	 * void execute_code_with_environment_variables(
 724 | 	 * ) throws Exception {
 725 | 	 *   {@literal List} values = withEnvironmentVariable("first", "first value")
 726 | 	 *     .and("second", "second value")
 727 | 	 *     .and("third", null)
 728 | 	 *     .execute((){@literal ->} asList(
 729 | 	 *         System.getenv("first"),
 730 | 	 *         System.getenv("second"),
 731 | 	 *         System.getenv("third")
 732 | 	 *     ));
 733 | 	 *   assertEquals(
 734 | 	 *     asList("first value", "second value", null),
 735 | 	 *     values
 736 | 	 *   );
 737 | 	 * }
 738 | 	 * 
739 | *

You cannot specify the value of an an environment variable twice. An 740 | * {@code IllegalArgumentException} is thrown when you try. 741 | *

Warning: This method uses reflection for modifying internals of the 742 | * environment variables map. It fails if your {@code SecurityManager} forbids 743 | * such modifications. 744 | * @param name the name of the environment variable. 745 | * @param value the value of the environment variable. 746 | * @return an {@link WithEnvironmentVariables} instance that can be used to 747 | * set more variables and run a statement with the specified environment 748 | * variables. 749 | * @since 1.0.0 750 | * @see WithEnvironmentVariables#and(String, String) 751 | * @see WithEnvironmentVariables#execute(Callable) 752 | * @see WithEnvironmentVariables#execute(Statement) 753 | */ 754 | public static WithEnvironmentVariables withEnvironmentVariable( 755 | String name, 756 | String value 757 | ) { 758 | return new WithEnvironmentVariables( 759 | singletonMap(name, value) 760 | ); 761 | } 762 | 763 | /** 764 | * Executes the statement with the provided security manager. 765 | *

 766 |      * @Test
 767 |      * void execute_code_with_specific_SecurityManager(
 768 | 	 * ) throws Exception {
 769 |      *   SecurityManager securityManager = new ASecurityManager();
 770 |      *   withSecurityManager(
 771 |      *     securityManager,
 772 |      *     (){@literal ->} {
 773 | 	 *       //code under test
 774 | 	 *       //e.g. the following assertion is met
 775 |      *       assertSame(securityManager, System.getSecurityManager())
 776 |      *     }
 777 |      *   );
 778 |      * }
 779 |      * 
780 | * The specified security manager is only present during the test. 781 | * @param securityManager the security manager that is used while the 782 | * statement is executed. 783 | * @param statement an arbitrary piece of code. 784 | * @throws Exception any exception thrown by the statement. 785 | * @since 1.0.0 786 | */ 787 | public static void withSecurityManager( 788 | SecurityManager securityManager, 789 | Statement statement 790 | ) throws Exception { 791 | SecurityManager originalSecurityManager = getSecurityManager(); 792 | setSecurityManager(securityManager); 793 | try { 794 | statement.execute(); 795 | } finally { 796 | setSecurityManager(originalSecurityManager); 797 | } 798 | } 799 | 800 | /** 801 | * Executes the statement and lets {@code System.in} provide the specified 802 | * text during the execution. In addition several Exceptions can be 803 | * specified that are thrown when {@code System.in#read} is called. 804 | * 805 | *
 806 | 	 * @Test
 807 | 	 * void Scanner_reads_text_from_System_in(
 808 | 	 * ) throws Exception {
 809 | 	 *   withTextFromSystemIn("first line", "second line")
 810 | 	 *     .execute((){@literal ->} {
 811 | 	 *       Scanner scanner = new Scanner(System.in);
 812 | 	 *       scanner.nextLine();
 813 | 	 *       assertEquals("first line", scanner.nextLine());
 814 | 	 *     });
 815 | 	 * }
 816 | 	 * 
817 | * 818 | *

Throwing Exceptions

819 | *

You can also simulate a {@code System.in} that throws an 820 | * {@code IOException} or {@code RuntimeException}. Use 821 | * 822 | *

 823 | 	 * @Test
 824 | 	 * void System_in_throws_IOException(
 825 | 	 * ) throws Exception {
 826 | 	 *   withTextFromSystemIn()
 827 | 	 *     .andExceptionThrownOnInputEnd(new IOException())
 828 | 	 *     .execute((){@literal ->} {
 829 | 	 *       assertThrows(
 830 | 	 *         IOException.class,
 831 | 	 *         (){@literal ->} new Scanner(System.in).readLine())
 832 | 	 *       );
 833 | 	 *     )};
 834 | 	 * }
 835 | 	 *
 836 | 	 * @Test
 837 | 	 * void System_in_throws_RuntimeException(
 838 | 	 * ) throws Exception {
 839 | 	 *   withTextFromSystemIn()
 840 | 	 *    .andExceptionThrownOnInputEnd(new RuntimeException())
 841 | 	 *    .execute((){@literal ->} {
 842 | 	 *       assertThrows(
 843 | 	 *         RuntimeException.class,
 844 | 	 *         (){@literal ->} new Scanner(System.in).readLine())
 845 | 	 *       );
 846 | 	 *     )};
 847 | 	 * }
 848 | 	 * 
849 | *

If you provide text as parameters of {@code withTextFromSystemIn(...)} 850 | * in addition then the exception is thrown after the text has been read 851 | * from {@code System.in}. 852 | * @param lines the lines that are available from {@code System.in}. 853 | * @return an {@link SystemInStub} instance that is used to execute a 854 | * statement with its {@link SystemInStub#execute(Statement) execute} 855 | * method. In addition it can be used to specify an exception that is thrown 856 | * after the text is read. 857 | * @since 1.0.0 858 | * @see SystemInStub#execute(Statement) 859 | * @see SystemInStub#andExceptionThrownOnInputEnd(IOException) 860 | * @see SystemInStub#andExceptionThrownOnInputEnd(RuntimeException) 861 | */ 862 | public static SystemInStub withTextFromSystemIn( 863 | String... lines 864 | ) { 865 | String text = stream(lines) 866 | .map(line -> line + lineSeparator()) 867 | .collect(joining()); 868 | return new SystemInStub(text); 869 | } 870 | 871 | private static Properties copyOf( 872 | Properties source 873 | ) { 874 | Properties copy = new Properties(); 875 | copy.putAll(source); 876 | return copy; 877 | } 878 | 879 | private static void executeWithSystemErrReplacement( 880 | OutputStream replacementForErr, 881 | Statement statement 882 | ) throws Exception { 883 | PrintStream originalStream = err; 884 | try { 885 | setErr(wrap(replacementForErr)); 886 | statement.execute(); 887 | } finally { 888 | setErr(originalStream); 889 | } 890 | } 891 | 892 | private static void executeWithSystemOutReplacement( 893 | OutputStream replacementForOut, 894 | Statement statement 895 | ) throws Exception { 896 | PrintStream originalStream = out; 897 | try { 898 | setOut(wrap(replacementForOut)); 899 | statement.execute(); 900 | } finally { 901 | setOut(originalStream); 902 | } 903 | } 904 | 905 | private static PrintStream wrap( 906 | OutputStream outputStream 907 | ) throws UnsupportedEncodingException { 908 | return new PrintStream( 909 | outputStream, 910 | AUTO_FLUSH, 911 | DEFAULT_ENCODING 912 | ); 913 | } 914 | 915 | private static class DisallowWriteStream extends OutputStream { 916 | @Override 917 | public void write( 918 | int b 919 | ) { 920 | throw new AssertionError( 921 | "Tried to write '" 922 | + (char) b 923 | + "' although this is not allowed." 924 | ); 925 | } 926 | } 927 | 928 | private static class NoopStream extends OutputStream { 929 | @Override 930 | public void write( 931 | int b 932 | ) { 933 | } 934 | } 935 | 936 | /** 937 | * A stub that defines the text provided by {@code System.in}. The methods 938 | * {@link #andExceptionThrownOnInputEnd(IOException)} and 939 | * {@link #andExceptionThrownOnInputEnd(RuntimeException)} can be used to 940 | * simulate a {@code System.in} that throws an exception. 941 | *

The specified behaviour of {@code System.in} is applied to an 942 | * arbitrary piece of code that is provided to {@link #execute(Statement)}. 943 | */ 944 | public static class SystemInStub { 945 | private IOException ioException; 946 | private RuntimeException runtimeException; 947 | private final String text; 948 | 949 | private SystemInStub( 950 | String text 951 | ) { 952 | this.text = text; 953 | } 954 | 955 | /** 956 | * Sets an exception that is thrown after the text is read. 957 | * @param exception the {@code IOException} to be thrown. 958 | * @return the {@code SystemInStub} itself. 959 | * @throws IllegalStateException if a {@code RuntimeException} was 960 | * already set by 961 | * {@link #andExceptionThrownOnInputEnd(RuntimeException)} 962 | */ 963 | public SystemInStub andExceptionThrownOnInputEnd( 964 | IOException exception 965 | ) { 966 | if (runtimeException != null) 967 | throw new IllegalStateException("You cannot call" 968 | + " andExceptionThrownOnInputEnd(IOException) because" 969 | + " andExceptionThrownOnInputEnd(RuntimeException) has" 970 | + " already been called."); 971 | this.ioException = exception; 972 | return this; 973 | } 974 | 975 | /** 976 | * Sets an exception that is thrown after the text is read. 977 | * @param exception the {@code RuntimeException} to be thrown. 978 | * @return the {@code SystemInStub} itself. 979 | * @throws IllegalStateException if an {@code IOException} was already 980 | * set by {@link #andExceptionThrownOnInputEnd(IOException)} 981 | */ 982 | public SystemInStub andExceptionThrownOnInputEnd( 983 | RuntimeException exception 984 | ) { 985 | if (ioException != null) 986 | throw new IllegalStateException("You cannot call" 987 | + " andExceptionThrownOnInputEnd(RuntimeException) because" 988 | + " andExceptionThrownOnInputEnd(IOException) has already" 989 | + " been called."); 990 | this.runtimeException = exception; 991 | return this; 992 | } 993 | 994 | /** 995 | * Executes the statement and lets {@code System.in} provide the 996 | * specified text during the execution. After the text was read it 997 | * throws and exception when {@code System.in#read} is called and an 998 | * exception was specified by 999 | * {@link #andExceptionThrownOnInputEnd(IOException)} or 1000 | * {@link #andExceptionThrownOnInputEnd(RuntimeException)}. 1001 | * @param statement an arbitrary piece of code. 1002 | * @throws Exception any exception thrown by the statement. 1003 | */ 1004 | public void execute( 1005 | Statement statement 1006 | ) throws Exception { 1007 | InputStream stubStream = new ReplacementInputStream( 1008 | text, ioException, runtimeException 1009 | ); 1010 | InputStream originalIn = System.in; 1011 | try { 1012 | setIn(stubStream); 1013 | statement.execute(); 1014 | } finally { 1015 | setIn(originalIn); 1016 | } 1017 | } 1018 | 1019 | 1020 | private static class ReplacementInputStream extends InputStream { 1021 | private final StringReader reader; 1022 | private final IOException ioException; 1023 | private final RuntimeException runtimeException; 1024 | 1025 | ReplacementInputStream( 1026 | String text, 1027 | IOException ioException, 1028 | RuntimeException runtimeException 1029 | ) { 1030 | this.reader = new StringReader(text); 1031 | this.ioException = ioException; 1032 | this.runtimeException = runtimeException; 1033 | } 1034 | 1035 | @Override 1036 | public int read( 1037 | ) throws IOException { 1038 | int character = reader.read(); 1039 | if (character == -1) 1040 | handleEmptyReader(); 1041 | return character; 1042 | } 1043 | 1044 | private void handleEmptyReader( 1045 | ) throws IOException { 1046 | if (ioException != null) 1047 | throw ioException; 1048 | else if (runtimeException != null) 1049 | throw runtimeException; 1050 | } 1051 | 1052 | @Override 1053 | public int read( 1054 | byte[] buffer, 1055 | int offset, 1056 | int len 1057 | ) throws IOException { 1058 | if (buffer == null) 1059 | throw new NullPointerException(); 1060 | else if (offset < 0 || len < 0 || len > buffer.length - offset) 1061 | throw new IndexOutOfBoundsException(); 1062 | else if (len == 0) 1063 | return 0; 1064 | else 1065 | return readNextLine(buffer, offset, len); 1066 | } 1067 | 1068 | private int readNextLine( 1069 | byte[] buffer, 1070 | int offset, 1071 | int len 1072 | ) throws IOException { 1073 | int c = read(); 1074 | if (c == -1) 1075 | return -1; 1076 | buffer[offset] = (byte) c; 1077 | 1078 | int i = 1; 1079 | for (; (i < len) && !isCompleteLineWritten(buffer, i - 1); ++i) { 1080 | byte read = (byte) read(); 1081 | if (read == -1) 1082 | break; 1083 | else 1084 | buffer[offset + i] = read; 1085 | } 1086 | return i; 1087 | } 1088 | 1089 | private boolean isCompleteLineWritten( 1090 | byte[] buffer, 1091 | int indexLastByteWritten 1092 | ) { 1093 | byte[] separator = getProperty("line.separator") 1094 | .getBytes(defaultCharset()); 1095 | int indexFirstByteOfSeparator = indexLastByteWritten 1096 | - separator.length + 1; 1097 | return indexFirstByteOfSeparator >= 0 1098 | && contains(buffer, separator, indexFirstByteOfSeparator); 1099 | } 1100 | 1101 | private boolean contains( 1102 | byte[] array, 1103 | byte[] pattern, 1104 | int indexStart 1105 | ) { 1106 | for (int i = 0; i < pattern.length; ++i) 1107 | if (array[indexStart + i] != pattern[i]) 1108 | return false; 1109 | return true; 1110 | } 1111 | } 1112 | } 1113 | 1114 | private static class TapStream extends OutputStream { 1115 | final ByteArrayOutputStream text = new ByteArrayOutputStream(); 1116 | 1117 | @Override 1118 | public void write( 1119 | int b 1120 | ) { 1121 | text.write(b); 1122 | } 1123 | 1124 | String textThatWasWritten() { 1125 | return text.toString(); 1126 | } 1127 | } 1128 | 1129 | /** 1130 | * A collection of values for environment variables. New values can be 1131 | * added by {@link #and(String, String)}. The {@code EnvironmentVariables} 1132 | * object is then used to execute an arbitrary piece of code with these 1133 | * environment variables being present. 1134 | */ 1135 | public static final class WithEnvironmentVariables { 1136 | private final Map variables; 1137 | 1138 | private WithEnvironmentVariables( 1139 | Map variables 1140 | ) { 1141 | this.variables = variables; 1142 | } 1143 | 1144 | /** 1145 | * Creates a new {@code WithEnvironmentVariables} object that 1146 | * additionally stores the value for an additional environment variable. 1147 | *

You cannot specify the value of an environment variable twice. An 1148 | * {@code IllegalArgumentException} when you try. 1149 | * @param name the name of the environment variable. 1150 | * @param value the value of the environment variable. 1151 | * @return a new {@code WithEnvironmentVariables} object. 1152 | * @throws IllegalArgumentException when a value for the environment 1153 | * variable {@code name} is already specified. 1154 | * @see #withEnvironmentVariable(String, String) 1155 | * @see #execute(Statement) 1156 | */ 1157 | public WithEnvironmentVariables and( 1158 | String name, 1159 | String value 1160 | ) { 1161 | validateNotSet(name, value); 1162 | HashMap moreVariables = new HashMap<>(variables); 1163 | moreVariables.put(name, value); 1164 | return new WithEnvironmentVariables(moreVariables); 1165 | } 1166 | 1167 | private void validateNotSet( 1168 | String name, 1169 | String value 1170 | ) { 1171 | if (variables.containsKey(name)) { 1172 | String currentValue = variables.get(name); 1173 | throw new IllegalArgumentException( 1174 | "The environment variable '" + name + "' cannot be set to " 1175 | + format(value) + " because it was already set to " 1176 | + format(currentValue) + "." 1177 | ); 1178 | } 1179 | } 1180 | 1181 | private String format( 1182 | String text 1183 | ) { 1184 | if (text == null) 1185 | return "null"; 1186 | else 1187 | return "'" + text + "'"; 1188 | } 1189 | 1190 | /** 1191 | * Executes a {@code Callable} with environment variable values 1192 | * according to what was set before. It exposes the return value of the 1193 | * {@code Callable}. All changes to environment variables are reverted 1194 | * after the {@code Callable} has been executed. 1195 | *

1196 | 		 * @Test
1197 | 		 * void execute_code_with_environment_variables(
1198 | 		 * ) throws Exception {
1199 | 		 *   {@literal List} values = withEnvironmentVariable("first", "first value")
1200 | 		 *     .and("second", "second value")
1201 | 		 *     .and("third", null)
1202 | 		 *     .execute((){@literal ->} asList(
1203 | 		 *         System.getenv("first"),
1204 | 		 *         System.getenv("second"),
1205 | 		 *         System.getenv("third")
1206 | 		 *     ));
1207 | 		 *   assertEquals(
1208 | 		 *     asList("first value", "second value", null),
1209 | 		 *     values
1210 | 		 *   );
1211 | 		 * }
1212 | 		 * 
1213 | *

Warning: This method uses reflection for modifying internals of the 1214 | * environment variables map. It fails if your {@code SecurityManager} forbids 1215 | * such modifications. 1216 | * @param the type of {@code callable}'s result 1217 | * @param callable an arbitrary piece of code. 1218 | * @return the return value of {@code callable}. 1219 | * @throws Exception any exception thrown by the callable. 1220 | * @since 1.1.0 1221 | * @see #withEnvironmentVariable(String, String) 1222 | * @see #and(String, String) 1223 | * @see #execute(Statement) 1224 | */ 1225 | public T execute( 1226 | Callable callable 1227 | ) throws Exception { 1228 | Map originalVariables = new HashMap<>(getenv()); 1229 | try { 1230 | setEnvironmentVariables(); 1231 | return callable.call(); 1232 | } finally { 1233 | restoreOriginalVariables(originalVariables); 1234 | } 1235 | } 1236 | 1237 | /** 1238 | * Executes a statement with environment variable values according to 1239 | * what was set before. All changes to environment variables are 1240 | * reverted after the statement has been executed. 1241 | *

1242 | 		 * @Test
1243 | 		 * void execute_code_with_environment_variables(
1244 | 		 * ) throws Exception {
1245 | 		 *   withEnvironmentVariable("first", "first value")
1246 | 		 *     .and("second", "second value")
1247 | 		 *     .and("third", null)
1248 | 		 *     .execute((){@literal ->} {
1249 | 		 *       assertEquals(
1250 | 		 *         "first value",
1251 | 		 *         System.getenv("first")
1252 | 		 *       );
1253 | 		 *       assertEquals(
1254 | 		 *         "second value",
1255 | 		 *         System.getenv("second")
1256 | 		 *       );
1257 | 		 *       assertNull(
1258 | 		 *         System.getenv("third")
1259 | 		 *       );
1260 | 		 *     });
1261 | 		 * }
1262 | 		 * 
1263 | *

Warning: This method uses reflection for modifying internals of the 1264 | * environment variables map. It fails if your {@code SecurityManager} forbids 1265 | * such modifications. 1266 | * @param statement an arbitrary piece of code. 1267 | * @throws Exception any exception thrown by the statement. 1268 | * @since 1.0.0 1269 | * @see #withEnvironmentVariable(String, String) 1270 | * @see WithEnvironmentVariables#and(String, String) 1271 | * @see #execute(Callable) 1272 | */ 1273 | public void execute( 1274 | Statement statement 1275 | ) throws Exception { 1276 | Map originalVariables = new HashMap<>(getenv()); 1277 | try { 1278 | setEnvironmentVariables(); 1279 | statement.execute(); 1280 | } finally { 1281 | restoreOriginalVariables(originalVariables); 1282 | } 1283 | } 1284 | 1285 | private void setEnvironmentVariables() { 1286 | overrideVariables( 1287 | getEditableMapOfVariables() 1288 | ); 1289 | overrideVariables( 1290 | getTheCaseInsensitiveEnvironment() 1291 | ); 1292 | } 1293 | 1294 | private void overrideVariables( 1295 | Map existingVariables 1296 | ) { 1297 | if (existingVariables != null) //theCaseInsensitiveEnvironment may be null 1298 | variables.forEach( 1299 | (name, value) -> set(existingVariables, name, value) 1300 | ); 1301 | } 1302 | 1303 | private void set( 1304 | Map variables, 1305 | String name, 1306 | String value 1307 | ) { 1308 | if (value == null) 1309 | variables.remove(name); 1310 | else 1311 | variables.put(name, value); 1312 | } 1313 | 1314 | void restoreOriginalVariables( 1315 | Map originalVariables 1316 | ) { 1317 | restoreVariables( 1318 | getEditableMapOfVariables(), 1319 | originalVariables 1320 | ); 1321 | restoreVariables( 1322 | getTheCaseInsensitiveEnvironment(), 1323 | originalVariables 1324 | ); 1325 | } 1326 | 1327 | void restoreVariables( 1328 | Map variables, 1329 | Map originalVariables 1330 | ) { 1331 | if (variables != null) { //theCaseInsensitiveEnvironment may be null 1332 | variables.clear(); 1333 | variables.putAll(originalVariables); 1334 | } 1335 | } 1336 | 1337 | private static Map getEditableMapOfVariables() { 1338 | Class classOfMap = getenv().getClass(); 1339 | try { 1340 | return getFieldValue(classOfMap, getenv(), "m"); 1341 | } catch (IllegalAccessException e) { 1342 | throw new RuntimeException("System Rules cannot access the field" 1343 | + " 'm' of the map System.getenv().", e); 1344 | } catch (NoSuchFieldException e) { 1345 | throw new RuntimeException("System Rules expects System.getenv() to" 1346 | + " have a field 'm' but it has not.", e); 1347 | } 1348 | } 1349 | 1350 | /* 1351 | * The names of environment variables are case-insensitive in Windows. 1352 | * Therefore it stores the variables in a TreeMap named 1353 | * theCaseInsensitiveEnvironment. 1354 | */ 1355 | private static Map getTheCaseInsensitiveEnvironment() { 1356 | try { 1357 | Class processEnvironment = forName("java.lang.ProcessEnvironment"); 1358 | return getFieldValue( 1359 | processEnvironment, null, "theCaseInsensitiveEnvironment"); 1360 | } catch (ClassNotFoundException e) { 1361 | throw new RuntimeException("System Rules expects the existence of" 1362 | + " the class java.lang.ProcessEnvironment but it does not" 1363 | + " exist.", e); 1364 | } catch (IllegalAccessException e) { 1365 | throw new RuntimeException("System Rules cannot access the static" 1366 | + " field 'theCaseInsensitiveEnvironment' of the class" 1367 | + " java.lang.ProcessEnvironment.", e); 1368 | } catch (NoSuchFieldException e) { 1369 | //this field is only available for Windows 1370 | return null; 1371 | } 1372 | } 1373 | 1374 | private static Map getFieldValue( 1375 | Class klass, 1376 | Object object, 1377 | String name 1378 | ) throws NoSuchFieldException, IllegalAccessException { 1379 | Field field = klass.getDeclaredField(name); 1380 | field.setAccessible(true); 1381 | return (Map) field.get(object); 1382 | } 1383 | } 1384 | 1385 | private static int checkSystemExit( 1386 | NoExitSecurityManager securityManager 1387 | ) { 1388 | if (securityManager.isCheckExitCalled()) 1389 | return securityManager.getStatusOfFirstCheckExitCall(); 1390 | else 1391 | throw new AssertionError("System.exit has not been called."); 1392 | } 1393 | 1394 | private static class CheckExitCalled extends SecurityException { 1395 | private static final long serialVersionUID = 159678654L; 1396 | } 1397 | 1398 | /** 1399 | * A {@code NoExitSecurityManager} throws a {@link CheckExitCalled} 1400 | * exception whenever {@link #checkExit(int)} is called. All other method 1401 | * calls are delegated to the original security manager. 1402 | */ 1403 | private static class NoExitSecurityManager extends SecurityManager { 1404 | private final SecurityManager originalSecurityManager; 1405 | private Integer statusOfFirstExitCall = null; 1406 | 1407 | NoExitSecurityManager( 1408 | SecurityManager originalSecurityManager 1409 | ) { 1410 | this.originalSecurityManager = originalSecurityManager; 1411 | } 1412 | 1413 | @Override 1414 | public void checkExit( 1415 | int status 1416 | ) { 1417 | if (statusOfFirstExitCall == null) 1418 | statusOfFirstExitCall = status; 1419 | throw new CheckExitCalled(); 1420 | } 1421 | 1422 | boolean isCheckExitCalled() { 1423 | return statusOfFirstExitCall != null; 1424 | } 1425 | 1426 | int getStatusOfFirstCheckExitCall() { 1427 | if (isCheckExitCalled()) 1428 | return statusOfFirstExitCall; 1429 | else 1430 | throw new IllegalStateException( 1431 | "checkExit(int) has not been called."); 1432 | } 1433 | 1434 | @Override 1435 | public boolean getInCheck() { 1436 | return (originalSecurityManager != null) 1437 | && originalSecurityManager.getInCheck(); 1438 | } 1439 | 1440 | @Override 1441 | public Object getSecurityContext() { 1442 | return (originalSecurityManager == null) ? super.getSecurityContext() 1443 | : originalSecurityManager.getSecurityContext(); 1444 | } 1445 | 1446 | @Override 1447 | public void checkPermission( 1448 | Permission perm 1449 | ) { 1450 | if (originalSecurityManager != null) 1451 | originalSecurityManager.checkPermission(perm); 1452 | } 1453 | 1454 | @Override 1455 | public void checkPermission( 1456 | Permission perm, 1457 | Object context 1458 | ) { 1459 | if (originalSecurityManager != null) 1460 | originalSecurityManager.checkPermission(perm, context); 1461 | } 1462 | 1463 | @Override 1464 | public void checkCreateClassLoader() { 1465 | if (originalSecurityManager != null) 1466 | originalSecurityManager.checkCreateClassLoader(); 1467 | } 1468 | 1469 | @Override 1470 | public void checkAccess( 1471 | Thread t 1472 | ) { 1473 | if (originalSecurityManager != null) 1474 | originalSecurityManager.checkAccess(t); 1475 | } 1476 | 1477 | @Override 1478 | public void checkAccess( 1479 | ThreadGroup g 1480 | ) { 1481 | if (originalSecurityManager != null) 1482 | originalSecurityManager.checkAccess(g); 1483 | } 1484 | 1485 | @Override 1486 | public void checkExec( 1487 | String cmd 1488 | ) { 1489 | if (originalSecurityManager != null) 1490 | originalSecurityManager.checkExec(cmd); 1491 | } 1492 | 1493 | @Override 1494 | public void checkLink( 1495 | String lib 1496 | ) { 1497 | if (originalSecurityManager != null) 1498 | originalSecurityManager.checkLink(lib); 1499 | } 1500 | 1501 | @Override 1502 | public void checkRead( 1503 | FileDescriptor fd 1504 | ) { 1505 | if (originalSecurityManager != null) 1506 | originalSecurityManager.checkRead(fd); 1507 | } 1508 | 1509 | @Override 1510 | public void checkRead( 1511 | String file 1512 | ) { 1513 | if (originalSecurityManager != null) 1514 | originalSecurityManager.checkRead(file); 1515 | } 1516 | 1517 | @Override 1518 | public void checkRead( 1519 | String file, 1520 | Object context 1521 | ) { 1522 | if (originalSecurityManager != null) 1523 | originalSecurityManager.checkRead(file, context); 1524 | } 1525 | 1526 | @Override 1527 | public void checkWrite( 1528 | FileDescriptor fd 1529 | ) { 1530 | if (originalSecurityManager != null) 1531 | originalSecurityManager.checkWrite(fd); 1532 | } 1533 | 1534 | @Override 1535 | public void checkWrite( 1536 | String file 1537 | ) { 1538 | if (originalSecurityManager != null) 1539 | originalSecurityManager.checkWrite(file); 1540 | } 1541 | 1542 | @Override 1543 | public void checkDelete( 1544 | String file 1545 | ) { 1546 | if (originalSecurityManager != null) 1547 | originalSecurityManager.checkDelete(file); 1548 | } 1549 | 1550 | @Override 1551 | public void checkConnect( 1552 | String host, 1553 | int port 1554 | ) { 1555 | if (originalSecurityManager != null) 1556 | originalSecurityManager.checkConnect(host, port); 1557 | } 1558 | 1559 | @Override 1560 | public void checkConnect( 1561 | String host, 1562 | int port, 1563 | Object context 1564 | ) { 1565 | if (originalSecurityManager != null) 1566 | originalSecurityManager.checkConnect(host, port, context); 1567 | } 1568 | 1569 | @Override 1570 | public void checkListen( 1571 | int port 1572 | ) { 1573 | if (originalSecurityManager != null) 1574 | originalSecurityManager.checkListen(port); 1575 | } 1576 | 1577 | @Override 1578 | public void checkAccept( 1579 | String host, 1580 | int port 1581 | ) { 1582 | if (originalSecurityManager != null) 1583 | originalSecurityManager.checkAccept(host, port); 1584 | } 1585 | 1586 | @Override 1587 | public void checkMulticast( 1588 | InetAddress maddr 1589 | ) { 1590 | if (originalSecurityManager != null) 1591 | originalSecurityManager.checkMulticast(maddr); 1592 | } 1593 | 1594 | @Override 1595 | public void checkMulticast( 1596 | InetAddress maddr, 1597 | byte ttl 1598 | ) { 1599 | if (originalSecurityManager != null) 1600 | originalSecurityManager.checkMulticast(maddr, ttl); 1601 | } 1602 | 1603 | @Override 1604 | public void checkPropertiesAccess() { 1605 | if (originalSecurityManager != null) 1606 | originalSecurityManager.checkPropertiesAccess(); 1607 | } 1608 | 1609 | @Override 1610 | public void checkPropertyAccess( 1611 | String key 1612 | ) { 1613 | if (originalSecurityManager != null) 1614 | originalSecurityManager.checkPropertyAccess(key); 1615 | } 1616 | 1617 | @Override 1618 | public boolean checkTopLevelWindow( 1619 | Object window 1620 | ) { 1621 | return (originalSecurityManager == null) ? super.checkTopLevelWindow(window) 1622 | : originalSecurityManager.checkTopLevelWindow(window); 1623 | } 1624 | 1625 | @Override 1626 | public void checkPrintJobAccess() { 1627 | if (originalSecurityManager != null) 1628 | originalSecurityManager.checkPrintJobAccess(); 1629 | } 1630 | 1631 | @Override 1632 | public void checkSystemClipboardAccess() { 1633 | if (originalSecurityManager != null) 1634 | originalSecurityManager.checkSystemClipboardAccess(); 1635 | } 1636 | 1637 | @Override 1638 | public void checkAwtEventQueueAccess() { 1639 | if (originalSecurityManager != null) 1640 | originalSecurityManager.checkAwtEventQueueAccess(); 1641 | } 1642 | 1643 | @Override 1644 | public void checkPackageAccess( 1645 | String pkg 1646 | ) { 1647 | if (originalSecurityManager != null) 1648 | originalSecurityManager.checkPackageAccess(pkg); 1649 | } 1650 | 1651 | @Override 1652 | public void checkPackageDefinition( 1653 | String pkg 1654 | ) { 1655 | if (originalSecurityManager != null) 1656 | originalSecurityManager.checkPackageDefinition(pkg); 1657 | } 1658 | 1659 | @Override 1660 | public void checkSetFactory() { 1661 | if (originalSecurityManager != null) 1662 | originalSecurityManager.checkSetFactory(); 1663 | } 1664 | 1665 | @Override 1666 | public void checkMemberAccess( 1667 | Class clazz, 1668 | int which 1669 | ) { 1670 | if (originalSecurityManager != null) 1671 | originalSecurityManager.checkMemberAccess(clazz, which); 1672 | } 1673 | 1674 | @Override 1675 | public void checkSecurityAccess( 1676 | String target 1677 | ) { 1678 | if (originalSecurityManager != null) 1679 | originalSecurityManager.checkSecurityAccess(target); 1680 | } 1681 | 1682 | @Override 1683 | public ThreadGroup getThreadGroup() { 1684 | return (originalSecurityManager == null) ? super.getThreadGroup() 1685 | : originalSecurityManager.getThreadGroup(); 1686 | } 1687 | } 1688 | } 1689 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/AssertNothingWrittenToSystemErrTest.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import java.util.Locale; 4 | 5 | import org.junit.jupiter.api.DisplayNameGeneration; 6 | import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; 7 | import org.junit.jupiter.api.Nested; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static com.github.stefanbirkner.systemlambda.SystemLambda.assertNothingWrittenToSystemErr; 11 | import static java.lang.System.getProperty; 12 | import static java.util.Locale.CANADA; 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | import static org.assertj.core.api.Assertions.catchThrowable; 15 | 16 | @DisplayNameGeneration(ReplaceUnderscores.class) 17 | class AssertNothingWrittenToSystemErrTest { 18 | private static final Locale DUMMY_LOCALE = CANADA; 19 | 20 | @Test 21 | void execution_of_statement_is_not_intercepted_when_it_does_not_write_to_System_err( 22 | ) throws Exception { 23 | assertNothingWrittenToSystemErr( 24 | () -> {} 25 | ); 26 | } 27 | 28 | @Nested 29 | class throws_AssertionError_when_statement { 30 | @Test 31 | void tries_to_append_a_text_to_System_err( 32 | ) { 33 | Throwable exception = catchThrowable( 34 | () -> assertNothingWrittenToSystemErr( 35 | () -> System.err.append("dummy text") 36 | ) 37 | ); 38 | assertThat(exception) 39 | .isInstanceOf(AssertionError.class) 40 | .hasMessage("Tried to write 'd' although this is not allowed."); 41 | } 42 | 43 | @Test 44 | void tries_to_append_a_character_to_System_err( 45 | ) { 46 | Throwable exception = catchThrowable( 47 | () -> assertNothingWrittenToSystemErr( 48 | () -> System.err.append('x') 49 | ) 50 | ); 51 | assertThat(exception) 52 | .isInstanceOf(AssertionError.class) 53 | .hasMessage("Tried to write 'x' although this is not allowed."); 54 | } 55 | 56 | @Test 57 | void tries_to_append_a_sub_sequence_of_a_text_to_System_err( 58 | ) { 59 | Throwable exception = catchThrowable( 60 | () -> assertNothingWrittenToSystemErr( 61 | () -> System.err.append("dummy text", 2, 3) 62 | ) 63 | ); 64 | assertThat(exception) 65 | .isInstanceOf(AssertionError.class) 66 | .hasMessage("Tried to write 'm' although this is not allowed."); 67 | } 68 | 69 | @Test 70 | void calls_System_err_format_with_a_Locale( 71 | ) { 72 | Throwable exception = catchThrowable( 73 | () -> assertNothingWrittenToSystemErr( 74 | () -> System.err.format( 75 | DUMMY_LOCALE, "%s, %s", "first dummy", "second dummy") 76 | ) 77 | ); 78 | assertThat(exception) 79 | .isInstanceOf(AssertionError.class) 80 | .hasMessage("Tried to write 'f' although this is not allowed."); 81 | } 82 | 83 | @Test 84 | void calls_System_err_format_without_a_Locale( 85 | ) { 86 | Throwable exception = catchThrowable( 87 | () -> assertNothingWrittenToSystemErr( 88 | () -> System.err.format( 89 | "%s, %s", "first dummy", "second dummy") 90 | ) 91 | ); 92 | assertThat(exception) 93 | .isInstanceOf(AssertionError.class) 94 | .hasMessage("Tried to write 'f' although this is not allowed."); 95 | } 96 | 97 | @Test 98 | void calls_System_err_print_with_a_boolean( 99 | ) { 100 | Throwable exception = catchThrowable( 101 | () -> assertNothingWrittenToSystemErr( 102 | () -> System.err.print(true) 103 | ) 104 | ); 105 | assertThat(exception) 106 | .isInstanceOf(AssertionError.class) 107 | .hasMessage("Tried to write 't' although this is not allowed."); 108 | } 109 | 110 | @Test 111 | void calls_System_err_print_with_a_char( 112 | ) { 113 | Throwable exception = catchThrowable( 114 | () -> assertNothingWrittenToSystemErr( 115 | () -> System.err.print('a') 116 | ) 117 | ); 118 | 119 | assertThat(exception) 120 | .isInstanceOf(AssertionError.class) 121 | .hasMessage("Tried to write 'a' although this is not allowed."); 122 | } 123 | 124 | @Test 125 | void calls_System_err_print_with_an_array_of_chars( 126 | ) { 127 | Throwable exception = catchThrowable( 128 | () -> assertNothingWrittenToSystemErr( 129 | () -> System.err.print(new char[]{'d', 'u', 'm', 'm', 'y'}) 130 | ) 131 | ); 132 | 133 | assertThat(exception) 134 | .isInstanceOf(AssertionError.class) 135 | .hasMessage("Tried to write 'd' although this is not allowed."); 136 | } 137 | 138 | @Test 139 | void calls_System_err_print_with_a_double( 140 | ) { 141 | Throwable exception = catchThrowable( 142 | () -> assertNothingWrittenToSystemErr( 143 | () -> System.err.print(1d) 144 | ) 145 | ); 146 | 147 | assertThat(exception) 148 | .isInstanceOf(AssertionError.class) 149 | .hasMessage("Tried to write '1' although this is not allowed."); 150 | } 151 | 152 | @Test 153 | void calls_System_err_print_with_a_float( 154 | ) { 155 | Throwable exception = catchThrowable( 156 | () -> assertNothingWrittenToSystemErr( 157 | () -> System.err.print(1f) 158 | ) 159 | ); 160 | 161 | assertThat(exception) 162 | .isInstanceOf(AssertionError.class) 163 | .hasMessage("Tried to write '1' although this is not allowed."); 164 | } 165 | 166 | @Test 167 | void calls_System_err_print_with_an_int( 168 | ) { 169 | Throwable exception = catchThrowable( 170 | () -> assertNothingWrittenToSystemErr( 171 | () -> System.err.print(1) 172 | ) 173 | ); 174 | 175 | assertThat(exception) 176 | .isInstanceOf(AssertionError.class) 177 | .hasMessage("Tried to write '1' although this is not allowed."); 178 | } 179 | 180 | @Test 181 | void calls_System_err_print_with_a_long( 182 | ) { 183 | Throwable exception = catchThrowable( 184 | () -> assertNothingWrittenToSystemErr( 185 | () -> System.err.print(1L) 186 | ) 187 | ); 188 | 189 | assertThat(exception) 190 | .isInstanceOf(AssertionError.class) 191 | .hasMessage("Tried to write '1' although this is not allowed."); 192 | } 193 | 194 | @Test 195 | void calls_System_err_print_with_an_object( 196 | ) { 197 | Throwable exception = catchThrowable( 198 | () -> assertNothingWrittenToSystemErr( 199 | () -> System.err.print(new Object()) 200 | ) 201 | ); 202 | 203 | assertThat(exception) 204 | .isInstanceOf(AssertionError.class) 205 | .hasMessage("Tried to write 'j' although this is not allowed."); 206 | } 207 | 208 | @Test 209 | void calls_System_err_print_with_a_string( 210 | ) { 211 | Throwable exception = catchThrowable( 212 | () -> assertNothingWrittenToSystemErr( 213 | () -> System.err.print("dummy") 214 | ) 215 | ); 216 | 217 | assertThat(exception) 218 | .isInstanceOf(AssertionError.class) 219 | .hasMessage("Tried to write 'd' although this is not allowed."); 220 | } 221 | 222 | @Test 223 | void calls_System_err_printf_with_a_localized_formatted_text( 224 | ) { 225 | Throwable exception = catchThrowable( 226 | () -> assertNothingWrittenToSystemErr( 227 | () -> System.err.printf( 228 | DUMMY_LOCALE, "%s, %s", "first dummy", "second dummy" 229 | ) 230 | ) 231 | ); 232 | 233 | assertThat(exception) 234 | .isInstanceOf(AssertionError.class) 235 | .hasMessage("Tried to write 'f' although this is not allowed."); 236 | } 237 | 238 | @Test 239 | void calls_System_err_printf_with_a_formatted_text( 240 | ) { 241 | Throwable exception = catchThrowable( 242 | () -> assertNothingWrittenToSystemErr( 243 | () -> System.err.printf( 244 | "%s, %s", "first dummy", "second dummy" 245 | ) 246 | ) 247 | ); 248 | 249 | assertThat(exception) 250 | .isInstanceOf(AssertionError.class) 251 | .hasMessage("Tried to write 'f' although this is not allowed."); 252 | } 253 | 254 | @Test 255 | void calls_println_on_System_err( 256 | ) { 257 | Throwable exception = catchThrowable( 258 | () -> assertNothingWrittenToSystemErr( 259 | () -> System.err.println() 260 | ) 261 | ); 262 | 263 | assertThat(exception) 264 | .isInstanceOf(AssertionError.class) 265 | .hasMessage( 266 | "Tried to write '" 267 | + getProperty("line.separator").substring(0, 1) 268 | + "' although this is not allowed." 269 | ); 270 | } 271 | 272 | @Test 273 | void calls_System_err_println_with_a_boolean( 274 | ) { 275 | Throwable exception = catchThrowable( 276 | () -> assertNothingWrittenToSystemErr( 277 | () -> System.err.println(true) 278 | ) 279 | ); 280 | 281 | assertThat(exception) 282 | .isInstanceOf(AssertionError.class) 283 | .hasMessage("Tried to write 't' although this is not allowed."); 284 | } 285 | 286 | @Test 287 | void calls_System_err_println_with_a_char( 288 | ) { 289 | Throwable exception = catchThrowable( 290 | () -> assertNothingWrittenToSystemErr( 291 | () -> System.err.println('a') 292 | ) 293 | ); 294 | 295 | assertThat(exception) 296 | .isInstanceOf(AssertionError.class) 297 | .hasMessage("Tried to write 'a' although this is not allowed."); 298 | } 299 | 300 | @Test 301 | void calls_System_err_println_with_an_array_of_chars( 302 | ) { 303 | Throwable exception = catchThrowable( 304 | () -> assertNothingWrittenToSystemErr( 305 | () -> System.err.println( 306 | new char[]{'d', 'u', 'm', 'm', 'y'} 307 | ) 308 | ) 309 | ); 310 | 311 | assertThat(exception) 312 | .isInstanceOf(AssertionError.class) 313 | .hasMessage("Tried to write 'd' although this is not allowed."); 314 | } 315 | 316 | @Test 317 | void calls_System_err_println_with_a_double( 318 | ) { 319 | Throwable exception = catchThrowable( 320 | () -> assertNothingWrittenToSystemErr( 321 | () -> System.err.println(1d) 322 | ) 323 | ); 324 | 325 | assertThat(exception) 326 | .isInstanceOf(AssertionError.class) 327 | .hasMessage("Tried to write '1' although this is not allowed."); 328 | } 329 | 330 | @Test 331 | void calls_System_err_println_with_a_float( 332 | ) { 333 | Throwable exception = catchThrowable( 334 | () -> assertNothingWrittenToSystemErr( 335 | () -> System.err.println(1f) 336 | ) 337 | ); 338 | 339 | assertThat(exception) 340 | .isInstanceOf(AssertionError.class) 341 | .hasMessage("Tried to write '1' although this is not allowed."); 342 | } 343 | 344 | @Test 345 | void calls_System_err_println_with_an_int( 346 | ) { 347 | Throwable exception = catchThrowable( 348 | () -> assertNothingWrittenToSystemErr( 349 | () -> System.err.println(1) 350 | ) 351 | ); 352 | 353 | assertThat(exception) 354 | .isInstanceOf(AssertionError.class) 355 | .hasMessage("Tried to write '1' although this is not allowed."); 356 | } 357 | 358 | @Test 359 | void calls_System_err_println_with_a_long( 360 | ) { 361 | Throwable exception = catchThrowable( 362 | () -> assertNothingWrittenToSystemErr( 363 | () -> System.err.println(1L) 364 | ) 365 | ); 366 | 367 | assertThat(exception) 368 | .isInstanceOf(AssertionError.class) 369 | .hasMessage("Tried to write '1' although this is not allowed."); 370 | } 371 | 372 | @Test 373 | void calls_System_err_println_with_an_object( 374 | ) { 375 | Throwable exception = catchThrowable( 376 | () -> assertNothingWrittenToSystemErr( 377 | () -> System.err.println(new Object()) 378 | ) 379 | ); 380 | 381 | assertThat(exception) 382 | .isInstanceOf(AssertionError.class) 383 | .hasMessage("Tried to write 'j' although this is not allowed."); 384 | } 385 | 386 | @Test 387 | void calls_System_err_println_with_a_string( 388 | ) { 389 | Throwable exception = catchThrowable( 390 | () -> assertNothingWrittenToSystemErr( 391 | () -> System.err.println("dummy") 392 | ) 393 | ); 394 | 395 | assertThat(exception) 396 | .isInstanceOf(AssertionError.class) 397 | .hasMessage("Tried to write 'd' although this is not allowed."); 398 | } 399 | } 400 | 401 | @Test 402 | void exception_thrown_by_statement_is_rethrown( 403 | ) { 404 | Exception exception = new Exception("some exception"); 405 | Throwable rethrownException = catchThrowable( 406 | () -> assertNothingWrittenToSystemErr( 407 | () -> { 408 | throw exception; 409 | } 410 | ) 411 | ); 412 | assertThat(rethrownException).isSameAs(exception); 413 | } 414 | 415 | @Test 416 | void statement_is_executed( 417 | ) throws Exception { 418 | StatementMock statementMock = new StatementMock(); 419 | assertNothingWrittenToSystemErr( 420 | statementMock 421 | ); 422 | assertThat(statementMock.hasBeenEvaluated).isTrue(); 423 | } 424 | 425 | @Nested 426 | class System_err_is_same_as_before 427 | extends RestoreSystemErrChecks 428 | { 429 | System_err_is_same_as_before() { 430 | super(SystemLambda::assertNothingWrittenToSystemErr); 431 | } 432 | } 433 | } 434 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/AssertNothingWrittenToSystemOutTest.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import java.util.Locale; 4 | 5 | import org.junit.jupiter.api.DisplayNameGeneration; 6 | import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; 7 | import org.junit.jupiter.api.Nested; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static com.github.stefanbirkner.systemlambda.SystemLambda.assertNothingWrittenToSystemOut; 11 | import static java.lang.System.getProperty; 12 | import static java.util.Locale.CANADA; 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | import static org.assertj.core.api.Assertions.catchThrowable; 15 | 16 | @DisplayNameGeneration(ReplaceUnderscores.class) 17 | class AssertNothingWrittenToSystemOutTest { 18 | private static final Locale DUMMY_LOCALE = CANADA; 19 | 20 | @Test 21 | void execution_of_statement_is_not_intercepted_when_it_does_not_write_to_System_out( 22 | ) throws Exception { 23 | assertNothingWrittenToSystemOut( 24 | () -> {} 25 | ); 26 | } 27 | 28 | @Nested 29 | class throws_AssertionError_when_statement { 30 | @Test 31 | void tries_to_append_a_text_to_System_out( 32 | ) { 33 | Throwable exception = catchThrowable( 34 | () -> assertNothingWrittenToSystemOut( 35 | () -> System.out.append("dummy text") 36 | ) 37 | ); 38 | assertThat(exception) 39 | .isInstanceOf(AssertionError.class) 40 | .hasMessage("Tried to write 'd' although this is not allowed."); 41 | } 42 | 43 | @Test 44 | void tries_to_append_a_character_to_System_out( 45 | ) { 46 | Throwable exception = catchThrowable( 47 | () -> assertNothingWrittenToSystemOut( 48 | () -> System.out.append('x') 49 | ) 50 | ); 51 | assertThat(exception) 52 | .isInstanceOf(AssertionError.class) 53 | .hasMessage("Tried to write 'x' although this is not allowed."); 54 | } 55 | 56 | @Test 57 | void tries_to_append_a_sub_sequence_of_a_text_to_System_out( 58 | ) { 59 | Throwable exception = catchThrowable( 60 | () -> assertNothingWrittenToSystemOut( 61 | () -> System.out.append("dummy text", 2, 3) 62 | ) 63 | ); 64 | assertThat(exception) 65 | .isInstanceOf(AssertionError.class) 66 | .hasMessage("Tried to write 'm' although this is not allowed."); 67 | } 68 | 69 | @Test 70 | void calls_System_out_format_with_a_Locale( 71 | ) { 72 | Throwable exception = catchThrowable( 73 | () -> assertNothingWrittenToSystemOut( 74 | () -> System.out.format( 75 | DUMMY_LOCALE, "%s, %s", "first dummy", "second dummy") 76 | ) 77 | ); 78 | assertThat(exception) 79 | .isInstanceOf(AssertionError.class) 80 | .hasMessage("Tried to write 'f' although this is not allowed."); 81 | } 82 | 83 | @Test 84 | void calls_System_out_format_without_a_Locale( 85 | ) { 86 | Throwable exception = catchThrowable( 87 | () -> assertNothingWrittenToSystemOut( 88 | () -> System.out.format( 89 | "%s, %s", "first dummy", "second dummy") 90 | ) 91 | ); 92 | assertThat(exception) 93 | .isInstanceOf(AssertionError.class) 94 | .hasMessage("Tried to write 'f' although this is not allowed."); 95 | } 96 | 97 | @Test 98 | void calls_System_out_print_with_a_boolean( 99 | ) { 100 | Throwable exception = catchThrowable( 101 | () -> assertNothingWrittenToSystemOut( 102 | () -> System.out.print(true) 103 | ) 104 | ); 105 | assertThat(exception) 106 | .isInstanceOf(AssertionError.class) 107 | .hasMessage("Tried to write 't' although this is not allowed."); 108 | } 109 | 110 | @Test 111 | void calls_System_out_print_with_a_char( 112 | ) { 113 | Throwable exception = catchThrowable( 114 | () -> assertNothingWrittenToSystemOut( 115 | () -> System.out.print('a') 116 | ) 117 | ); 118 | 119 | assertThat(exception) 120 | .isInstanceOf(AssertionError.class) 121 | .hasMessage("Tried to write 'a' although this is not allowed."); 122 | } 123 | 124 | @Test 125 | void calls_System_out_print_with_an_array_of_chars( 126 | ) { 127 | Throwable exception = catchThrowable( 128 | () -> assertNothingWrittenToSystemOut( 129 | () -> System.out.print(new char[]{'d', 'u', 'm', 'm', 'y'}) 130 | ) 131 | ); 132 | 133 | assertThat(exception) 134 | .isInstanceOf(AssertionError.class) 135 | .hasMessage("Tried to write 'd' although this is not allowed."); 136 | } 137 | 138 | @Test 139 | void calls_System_out_print_with_a_double( 140 | ) { 141 | Throwable exception = catchThrowable( 142 | () -> assertNothingWrittenToSystemOut( 143 | () -> System.out.print(1d) 144 | ) 145 | ); 146 | 147 | assertThat(exception) 148 | .isInstanceOf(AssertionError.class) 149 | .hasMessage("Tried to write '1' although this is not allowed."); 150 | } 151 | 152 | @Test 153 | void calls_System_out_print_with_a_float( 154 | ) { 155 | Throwable exception = catchThrowable( 156 | () -> assertNothingWrittenToSystemOut( 157 | () -> System.out.print(1f) 158 | ) 159 | ); 160 | 161 | assertThat(exception) 162 | .isInstanceOf(AssertionError.class) 163 | .hasMessage("Tried to write '1' although this is not allowed."); 164 | } 165 | 166 | @Test 167 | void calls_System_out_print_with_an_int( 168 | ) { 169 | Throwable exception = catchThrowable( 170 | () -> assertNothingWrittenToSystemOut( 171 | () -> System.out.print(1) 172 | ) 173 | ); 174 | 175 | assertThat(exception) 176 | .isInstanceOf(AssertionError.class) 177 | .hasMessage("Tried to write '1' although this is not allowed."); 178 | } 179 | 180 | @Test 181 | void calls_System_out_print_with_a_long( 182 | ) { 183 | Throwable exception = catchThrowable( 184 | () -> assertNothingWrittenToSystemOut( 185 | () -> System.out.print(1L) 186 | ) 187 | ); 188 | 189 | assertThat(exception) 190 | .isInstanceOf(AssertionError.class) 191 | .hasMessage("Tried to write '1' although this is not allowed."); 192 | } 193 | 194 | @Test 195 | void calls_System_out_print_with_an_object( 196 | ) { 197 | Throwable exception = catchThrowable( 198 | () -> assertNothingWrittenToSystemOut( 199 | () -> System.out.print(new Object()) 200 | ) 201 | ); 202 | 203 | assertThat(exception) 204 | .isInstanceOf(AssertionError.class) 205 | .hasMessage("Tried to write 'j' although this is not allowed."); 206 | } 207 | 208 | @Test 209 | void calls_System_out_print_with_a_string( 210 | ) { 211 | Throwable exception = catchThrowable( 212 | () -> assertNothingWrittenToSystemOut( 213 | () -> System.out.print("dummy") 214 | ) 215 | ); 216 | 217 | assertThat(exception) 218 | .isInstanceOf(AssertionError.class) 219 | .hasMessage("Tried to write 'd' although this is not allowed."); 220 | } 221 | 222 | @Test 223 | void calls_System_out_printf_with_a_localized_formatted_text( 224 | ) { 225 | Throwable exception = catchThrowable( 226 | () -> assertNothingWrittenToSystemOut( 227 | () -> System.out.printf( 228 | DUMMY_LOCALE, "%s, %s", "first dummy", "second dummy" 229 | ) 230 | ) 231 | ); 232 | 233 | assertThat(exception) 234 | .isInstanceOf(AssertionError.class) 235 | .hasMessage("Tried to write 'f' although this is not allowed."); 236 | } 237 | 238 | @Test 239 | void calls_System_out_printf_with_a_formatted_text( 240 | ) { 241 | Throwable exception = catchThrowable( 242 | () -> assertNothingWrittenToSystemOut( 243 | () -> System.out.printf("" + 244 | "%s, %s", "first dummy", "second dummy" 245 | ) 246 | ) 247 | ); 248 | 249 | assertThat(exception) 250 | .isInstanceOf(AssertionError.class) 251 | .hasMessage("Tried to write 'f' although this is not allowed."); 252 | } 253 | 254 | @Test 255 | void calls_println_on_System_out( 256 | ) { 257 | Throwable exception = catchThrowable( 258 | () -> assertNothingWrittenToSystemOut( 259 | () -> System.out.println() 260 | ) 261 | ); 262 | 263 | assertThat(exception) 264 | .isInstanceOf(AssertionError.class) 265 | .hasMessage( 266 | "Tried to write '" 267 | + getProperty("line.separator").substring(0, 1) 268 | + "' although this is not allowed." 269 | ); 270 | } 271 | 272 | @Test 273 | void calls_System_out_println_with_a_boolean( 274 | ) { 275 | Throwable exception = catchThrowable( 276 | () -> assertNothingWrittenToSystemOut( 277 | () -> System.out.println(true) 278 | ) 279 | ); 280 | 281 | assertThat(exception) 282 | .isInstanceOf(AssertionError.class) 283 | .hasMessage("Tried to write 't' although this is not allowed."); 284 | } 285 | 286 | @Test 287 | void calls_System_out_println_with_a_char( 288 | ) { 289 | Throwable exception = catchThrowable( 290 | () -> assertNothingWrittenToSystemOut( 291 | () -> System.out.println('a') 292 | ) 293 | ); 294 | 295 | assertThat(exception) 296 | .isInstanceOf(AssertionError.class) 297 | .hasMessage("Tried to write 'a' although this is not allowed."); 298 | } 299 | 300 | @Test 301 | void calls_System_out_println_with_an_array_of_chars( 302 | ) { 303 | Throwable exception = catchThrowable( 304 | () -> assertNothingWrittenToSystemOut( 305 | () -> System.out.println( 306 | new char[]{'d', 'u', 'm', 'm', 'y'} 307 | ) 308 | ) 309 | ); 310 | 311 | assertThat(exception) 312 | .isInstanceOf(AssertionError.class) 313 | .hasMessage("Tried to write 'd' although this is not allowed."); 314 | } 315 | 316 | @Test 317 | void calls_System_out_println_with_a_double( 318 | ) { 319 | Throwable exception = catchThrowable( 320 | () -> assertNothingWrittenToSystemOut( 321 | () -> System.out.println(1d) 322 | ) 323 | ); 324 | 325 | assertThat(exception) 326 | .isInstanceOf(AssertionError.class) 327 | .hasMessage("Tried to write '1' although this is not allowed."); 328 | } 329 | 330 | @Test 331 | void calls_System_out_println_with_a_float( 332 | ) { 333 | Throwable exception = catchThrowable( 334 | () -> assertNothingWrittenToSystemOut( 335 | () -> System.out.println(1f) 336 | ) 337 | ); 338 | 339 | assertThat(exception) 340 | .isInstanceOf(AssertionError.class) 341 | .hasMessage("Tried to write '1' although this is not allowed."); 342 | } 343 | 344 | @Test 345 | void calls_System_out_println_with_an_int( 346 | ) { 347 | Throwable exception = catchThrowable( 348 | () -> assertNothingWrittenToSystemOut( 349 | () -> System.out.println(1) 350 | ) 351 | ); 352 | 353 | assertThat(exception) 354 | .isInstanceOf(AssertionError.class) 355 | .hasMessage("Tried to write '1' although this is not allowed."); 356 | } 357 | 358 | @Test 359 | void calls_System_out_println_with_a_long( 360 | ) { 361 | Throwable exception = catchThrowable( 362 | () -> assertNothingWrittenToSystemOut( 363 | () -> System.out.println(1L) 364 | ) 365 | ); 366 | 367 | assertThat(exception) 368 | .isInstanceOf(AssertionError.class) 369 | .hasMessage("Tried to write '1' although this is not allowed."); 370 | } 371 | 372 | @Test 373 | void calls_System_out_println_with_an_object( 374 | ) { 375 | Throwable exception = catchThrowable( 376 | () -> assertNothingWrittenToSystemOut( 377 | () -> System.out.println(new Object()) 378 | ) 379 | ); 380 | 381 | assertThat(exception) 382 | .isInstanceOf(AssertionError.class) 383 | .hasMessage("Tried to write 'j' although this is not allowed."); 384 | } 385 | 386 | @Test 387 | void calls_System_out_println_with_a_string( 388 | ) { 389 | Throwable exception = catchThrowable( 390 | () -> assertNothingWrittenToSystemOut( 391 | () -> System.out.println("dummy") 392 | ) 393 | ); 394 | 395 | assertThat(exception) 396 | .isInstanceOf(AssertionError.class) 397 | .hasMessage("Tried to write 'd' although this is not allowed."); 398 | } 399 | } 400 | 401 | @Test 402 | void exception_thrown_by_statement_is_rethrown( 403 | ) { 404 | Exception exception = new Exception("some exception"); 405 | Throwable rethrownException = catchThrowable( 406 | () -> assertNothingWrittenToSystemOut( 407 | () -> { 408 | throw exception; 409 | } 410 | ) 411 | ); 412 | assertThat(rethrownException).isSameAs(exception); 413 | } 414 | 415 | @Test 416 | void statement_is_executed( 417 | ) throws Exception { 418 | StatementMock statementMock = new StatementMock(); 419 | assertNothingWrittenToSystemOut( 420 | statementMock 421 | ); 422 | assertThat(statementMock.hasBeenEvaluated).isTrue(); 423 | } 424 | 425 | @Nested 426 | class System_out_is_same_as_before 427 | extends RestoreSystemOutChecks 428 | { 429 | System_out_is_same_as_before() { 430 | super(SystemLambda::assertNothingWrittenToSystemOut); 431 | } 432 | } 433 | } 434 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/CallableMock.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import java.util.concurrent.Callable; 4 | 5 | class CallableMock implements Callable { 6 | boolean hasBeenEvaluated = false; 7 | 8 | @Override 9 | public String call() throws Exception { 10 | hasBeenEvaluated = true; 11 | return "dummy text"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/CatchSystemExitTest.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import static com.github.stefanbirkner.systemlambda.SystemLambda.catchSystemExit; 4 | import static com.github.stefanbirkner.systemlambda.SystemLambda.withSecurityManager; 5 | import static java.lang.System.exit; 6 | import static java.lang.System.getSecurityManager; 7 | import static java.net.InetAddress.getLocalHost; 8 | import static java.util.UUID.randomUUID; 9 | import static java.util.stream.Collectors.toList; 10 | import static org.assertj.core.api.Assertions.*; 11 | import static org.junit.jupiter.params.provider.Arguments.arguments; 12 | 13 | import org.junit.jupiter.api.DisplayNameGeneration; 14 | import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; 15 | import org.junit.jupiter.api.Nested; 16 | import org.junit.jupiter.api.Test; 17 | import org.junit.jupiter.api.extension.ExtensionContext; 18 | import org.junit.jupiter.params.ParameterizedTest; 19 | import org.junit.jupiter.params.provider.Arguments; 20 | import org.junit.jupiter.params.provider.ArgumentsProvider; 21 | import org.junit.jupiter.params.provider.ArgumentsSource; 22 | import com.github.stefanbirkner.systemlambda.SecurityManagerMock.Invocation; 23 | 24 | import java.io.FileDescriptor; 25 | import java.lang.reflect.Method; 26 | import java.net.InetAddress; 27 | import java.net.UnknownHostException; 28 | import java.security.AllPermission; 29 | import java.security.Permission; 30 | import java.util.*; 31 | import java.util.concurrent.atomic.AtomicBoolean; 32 | import java.util.concurrent.atomic.AtomicReference; 33 | import java.util.stream.Stream; 34 | 35 | @DisplayNameGeneration(ReplaceUnderscores.class) 36 | class CatchSystemExitTest { 37 | private static final int ARBITRARY_STATUS = 216843; 38 | 39 | @Nested 40 | class check_system_exit { 41 | @ParameterizedTest(name = "{0}") 42 | @ArgumentsSource(SecurityManagers.class) 43 | void status_provided_to_System_exit_is_made_available_when_called_in_same_thread( 44 | String description, 45 | SecurityManager securityManager 46 | ) throws Exception { 47 | withSecurityManager( 48 | securityManager, 49 | () -> { 50 | int status = catchSystemExit( 51 | () -> exit(ARBITRARY_STATUS) 52 | ); 53 | assertThat(status).isEqualTo(ARBITRARY_STATUS); 54 | } 55 | ); 56 | } 57 | 58 | @ParameterizedTest(name = "{0}") 59 | @ArgumentsSource(SecurityManagers.class) 60 | void status_provided_to_System_exit_is_made_available_when_called_in_another_thread( 61 | String description, 62 | SecurityManager securityManager 63 | ) throws Exception { 64 | withSecurityManager( 65 | securityManager, 66 | () -> { 67 | int status = catchSystemExit( 68 | () -> { 69 | Thread thread = new Thread( 70 | () -> exit(ARBITRARY_STATUS) 71 | ); 72 | thread.start(); 73 | thread.join(); 74 | } 75 | ); 76 | assertThat(status).isEqualTo(ARBITRARY_STATUS); 77 | } 78 | ); 79 | } 80 | 81 | @ParameterizedTest(name = "{0}") 82 | @ArgumentsSource(SecurityManagers.class) 83 | void test_fails_if_System_exit_is_not_called( 84 | String description, 85 | SecurityManager securityManager 86 | ) throws Exception { 87 | withSecurityManager( 88 | securityManager, 89 | () -> 90 | assertThatThrownBy( 91 | () -> catchSystemExit(() -> {}) 92 | ) 93 | .isInstanceOf(AssertionError.class) 94 | .hasMessage("System.exit has not been called.") 95 | ); 96 | } 97 | 98 | @ParameterizedTest(name = "{0}") 99 | @ArgumentsSource(SecurityManagers.class) 100 | void after_execution_the_security_manager_is_the_same_as_before( 101 | String description, 102 | SecurityManager securityManager 103 | ) throws Exception { 104 | AtomicReference managerAfterExecution 105 | = new AtomicReference<>(); 106 | withSecurityManager( 107 | securityManager, 108 | () -> { 109 | catchSystemExit(() -> exit(ARBITRARY_STATUS)); 110 | managerAfterExecution.set(getSecurityManager()); 111 | } 112 | ); 113 | assertThat(managerAfterExecution).hasValue(securityManager); 114 | } 115 | } 116 | 117 | @Nested 118 | class security_managers_public_methods { 119 | @ParameterizedTest(name = "{0}") 120 | @ArgumentsSource(SecurityManagerPublicMethods.class) 121 | void may_be_called_when_original_security_manager_is_missing( 122 | String description, 123 | Method method 124 | ) throws Exception { 125 | withSecurityManager( 126 | null, 127 | () -> catchSystemExit( 128 | () -> { 129 | method.invoke( 130 | getSecurityManager(), 131 | dummyArguments(method) 132 | ); 133 | //ensure that catchSystemExit does not fail 134 | exit(ARBITRARY_STATUS); 135 | } 136 | ) 137 | ); 138 | } 139 | 140 | @ParameterizedTest(name = "{0}") 141 | @ArgumentsSource(SecurityManagerPublicMethods.class) 142 | void is_delegated_to_original_security_manager_when_it_is_present( 143 | String testName, 144 | Method method 145 | ) throws Exception { 146 | SecurityManagerMock originalManager = new SecurityManagerMock(); 147 | Object[] arguments = dummyArguments(method); 148 | withSecurityManager( 149 | originalManager, 150 | () -> catchSystemExit( 151 | () -> { 152 | method.invoke(getSecurityManager(), arguments); 153 | //ensure that catchSystemExit does not fail 154 | exit(ARBITRARY_STATUS); 155 | } 156 | ) 157 | ); 158 | assertCallIsDelegated(originalManager, method, arguments); 159 | } 160 | 161 | private Object[] dummyArguments( 162 | Method method 163 | ) throws UnknownHostException { 164 | Class[] parameterTypes = method.getParameterTypes(); 165 | Object[] args = new Object[parameterTypes.length]; 166 | for (int i = 0; i < args.length; ++i) 167 | args[i] = dummy(parameterTypes[i]); 168 | return args; 169 | } 170 | 171 | private Object dummy( 172 | Class type 173 | ) throws UnknownHostException { 174 | if (type.getName().equals("int")) 175 | return new Random().nextInt(); 176 | else if (type.getName().equals("byte")) 177 | return (byte) new Random().nextInt(); 178 | else if (type.equals(String.class)) 179 | return randomUUID().toString(); 180 | else if (type.equals(Class.class)) 181 | return String.class; 182 | else if (type.equals(FileDescriptor.class)) 183 | return new FileDescriptor(); 184 | else if (type.equals(InetAddress.class)) 185 | return getLocalHost(); 186 | else if (type.equals(Object.class)) 187 | return new Object(); 188 | else if (type.equals(Permission.class)) 189 | return new AllPermission(); 190 | else if (type.equals(Thread.class)) 191 | return new Thread(); 192 | else if (type.equals(ThreadGroup.class)) 193 | return new ThreadGroup("arbitrary-thread-group"); 194 | else 195 | throw new IllegalArgumentException(type + " not supported."); 196 | } 197 | 198 | private void assertCallIsDelegated( 199 | SecurityManagerMock target, 200 | Method method, 201 | Object[] arguments 202 | ) { 203 | Collection invocations = invocationsForMethod( 204 | target, 205 | method 206 | ); 207 | assertThat(invocations) 208 | .withFailMessage("Method was not invoked.") 209 | .isNotEmpty(); 210 | assertThat(argumentsOf(invocations)) 211 | .contains(arguments); 212 | } 213 | 214 | private Collection invocationsForMethod( 215 | SecurityManagerMock target, 216 | Method method 217 | ) { 218 | return target.invocations 219 | .stream() 220 | .filter(invocation -> matchesMethod(invocation, method)) 221 | .collect(toList()); 222 | } 223 | 224 | private boolean matchesMethod( 225 | Invocation invocation, 226 | Method method 227 | ) { 228 | return Objects.equals( 229 | method.getName(), 230 | invocation.methodName 231 | ) 232 | && Arrays.equals( 233 | method.getParameterTypes(), 234 | invocation.parameterTypes 235 | ); 236 | } 237 | 238 | private Stream argumentsOf(Collection invocations) { 239 | return invocations.stream().map(invocation -> invocation.arguments); 240 | } 241 | } 242 | 243 | @Nested 244 | class security_managers_public_non_void_methods { 245 | private final SecurityManagerMock originalSecurityManager 246 | = new SecurityManagerMock(); 247 | 248 | @Test 249 | void getInCheck_is_delegated_to_original_security_manager( 250 | ) throws Exception { 251 | originalSecurityManager.inCheck = true; 252 | AtomicBoolean inCheck = new AtomicBoolean(); 253 | withSecurityManager( 254 | originalSecurityManager, 255 | () -> catchSystemExit( 256 | () -> { 257 | inCheck.set(getSecurityManager().getInCheck()); 258 | //ensure that catchSystemExit does not fail 259 | exit(ARBITRARY_STATUS); 260 | } 261 | ) 262 | ); 263 | assertThat(inCheck).isTrue(); 264 | } 265 | 266 | @Test 267 | void security_context_of_original_security_manager_is_provided( 268 | ) throws Exception { 269 | Object context = new Object(); 270 | originalSecurityManager.securityContext = context; 271 | AtomicReference contextDuringExecution = new AtomicReference<>(); 272 | withSecurityManager( 273 | originalSecurityManager, 274 | () -> catchSystemExit( 275 | () -> { 276 | contextDuringExecution.set( 277 | getSecurityManager().getSecurityContext() 278 | ); 279 | //ensure that catchSystemExit does not fail 280 | exit(ARBITRARY_STATUS); 281 | } 282 | ) 283 | ); 284 | assertThat(contextDuringExecution).hasValue(context); 285 | } 286 | 287 | @Test 288 | void checkTopLevelWindow_is_delegated_to_original_security_manager( 289 | ) throws Exception { 290 | originalSecurityManager.topLevelWindow = true; 291 | Object window = new Object(); 292 | AtomicBoolean check = new AtomicBoolean(); 293 | withSecurityManager( 294 | originalSecurityManager, 295 | () -> catchSystemExit( 296 | () -> { 297 | check.set( 298 | getSecurityManager().checkTopLevelWindow(window) 299 | ); 300 | //ensure that catchSystemExit does not fail 301 | exit(ARBITRARY_STATUS); 302 | } 303 | ) 304 | ); 305 | assertThat(check).isTrue(); 306 | assertThat(originalSecurityManager.windowOfCheckTopLevelWindowCall) 307 | .isSameAs(window); 308 | } 309 | 310 | @Test 311 | void thread_group_of_original_security_manager_is_provided( 312 | ) throws Exception { 313 | ThreadGroup threadGroup = new ThreadGroup("dummy name"); 314 | originalSecurityManager.threadGroup = threadGroup; 315 | AtomicReference threadGroupDuringExecution = new AtomicReference<>(); 316 | withSecurityManager( 317 | originalSecurityManager, 318 | () -> catchSystemExit( 319 | () -> { 320 | threadGroupDuringExecution.set( 321 | getSecurityManager().getThreadGroup()); 322 | //ensure that catchSystemExit does not fail 323 | exit(ARBITRARY_STATUS); 324 | } 325 | ) 326 | ); 327 | assertThat(threadGroupDuringExecution).hasValue(threadGroup); 328 | } 329 | } 330 | 331 | private static String join(Class[] types) { 332 | StringBuilder sb = new StringBuilder(); 333 | for (int i = 0; i < types.length; i++) { 334 | if (i != 0) 335 | sb.append(","); 336 | sb.append(types[i].getSimpleName()); 337 | } 338 | return sb.toString(); 339 | } 340 | 341 | private static class SecurityManagers implements ArgumentsProvider { 342 | @Override 343 | public Stream provideArguments( 344 | ExtensionContext extensionContext 345 | ) { 346 | return Stream.of( 347 | arguments( 348 | "with original SecurityManager", 349 | new SecurityManagerMock() 350 | ), 351 | arguments( 352 | "without original SecurityManager", 353 | null 354 | ) 355 | ); 356 | } 357 | } 358 | 359 | private static class SecurityManagerPublicMethods 360 | implements ArgumentsProvider 361 | { 362 | @Override 363 | public Stream provideArguments( 364 | ExtensionContext extensionContext 365 | ) { 366 | return Arrays.stream(SecurityManager.class.getMethods()) 367 | .filter(this::notDeclaredByObjectClass) 368 | .filter(this::notChangedByNoExitSecurityManager) 369 | .map(method -> arguments(testName(method), method)); 370 | } 371 | 372 | private boolean notDeclaredByObjectClass(Method method) { 373 | return !method.getDeclaringClass().equals(Object.class); 374 | } 375 | 376 | private boolean notChangedByNoExitSecurityManager(Method method) { 377 | return !method.getName().equals("checkExit"); 378 | } 379 | 380 | private String testName(Method method) { 381 | return method.getName() 382 | + "(" + join(method.getParameterTypes()) + ")"; 383 | } 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/MethodUnderTest.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | interface MethodUnderTest { 4 | void accept( 5 | Statement statement 6 | ) throws Exception; 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/MuteSystemErrTest.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.PrintStream; 5 | 6 | import org.junit.jupiter.api.DisplayNameGeneration; 7 | import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; 8 | import org.junit.jupiter.api.Nested; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import static com.github.stefanbirkner.systemlambda.SystemLambda.muteSystemErr; 12 | import static java.lang.System.*; 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | 15 | @DisplayNameGeneration(ReplaceUnderscores.class) 16 | class MuteSystemErrTest { 17 | 18 | @Test 19 | void no_text_is_written_to_System_err_by_statement( 20 | ) throws Exception { 21 | ByteArrayOutputStream captureOutputStream = new ByteArrayOutputStream(); 22 | setErr(new PrintStream(captureOutputStream)); 23 | muteSystemErr( 24 | () -> err.println("some text") 25 | ); 26 | assertThat(captureOutputStream) 27 | .hasToString(""); 28 | } 29 | 30 | @Nested 31 | class System_err_is_same_as_before 32 | extends RestoreSystemErrChecks 33 | { 34 | System_err_is_same_as_before() { 35 | super(SystemLambda::muteSystemErr); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/MuteSystemOutTest.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.PrintStream; 5 | 6 | import org.junit.jupiter.api.DisplayNameGeneration; 7 | import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; 8 | import org.junit.jupiter.api.Nested; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import static com.github.stefanbirkner.systemlambda.SystemLambda.muteSystemOut; 12 | import static java.lang.System.out; 13 | import static java.lang.System.setOut; 14 | import static org.assertj.core.api.Assertions.assertThat; 15 | 16 | @DisplayNameGeneration(ReplaceUnderscores.class) 17 | class MuteSystemOutTest { 18 | 19 | @Test 20 | void no_text_is_written_to_System_out_by_statement( 21 | ) throws Exception { 22 | ByteArrayOutputStream captureOutputStream = new ByteArrayOutputStream(); 23 | setOut(new PrintStream(captureOutputStream)); 24 | muteSystemOut( 25 | () -> out.println("some text") 26 | ); 27 | assertThat(captureOutputStream) 28 | .hasToString(""); 29 | } 30 | 31 | @Nested 32 | class System_out_is_same_as_before 33 | extends RestoreSystemOutChecks 34 | { 35 | System_out_is_same_as_before() { 36 | super(SystemLambda::muteSystemOut); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/RestoreSystemErrChecks.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.PrintStream; 6 | 7 | import static com.github.stefanbirkner.fishbowl.Fishbowl.ignoreException; 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | 10 | class RestoreSystemErrChecks { 11 | 12 | private final MethodUnderTest methodUnderTest; 13 | 14 | RestoreSystemErrChecks( 15 | MethodUnderTest methodUnderTest 16 | ) { 17 | this.methodUnderTest = methodUnderTest; 18 | } 19 | 20 | @Test 21 | void after_statement_is_executed( 22 | ) throws Exception { 23 | PrintStream originalErr = System.err; 24 | methodUnderTest.accept( 25 | () -> { 26 | } 27 | ); 28 | assertThat(System.err).isSameAs(originalErr); 29 | } 30 | 31 | @Test 32 | void after_statement_throws_exception() { 33 | PrintStream originalErr = System.err; 34 | ignoreException( 35 | () -> methodUnderTest.accept( 36 | () -> { 37 | throw new Exception("some exception"); 38 | } 39 | ) 40 | ); 41 | assertThat(System.err).isSameAs(originalErr); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/RestoreSystemOutChecks.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.PrintStream; 6 | 7 | import static com.github.stefanbirkner.fishbowl.Fishbowl.ignoreException; 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | 10 | class RestoreSystemOutChecks { 11 | 12 | private final MethodUnderTest methodUnderTest; 13 | 14 | RestoreSystemOutChecks( 15 | MethodUnderTest methodUnderTest 16 | ) { 17 | this.methodUnderTest = methodUnderTest; 18 | } 19 | 20 | @Test 21 | void after_statement_is_executed( 22 | ) throws Exception { 23 | PrintStream originalOut = System.out; 24 | methodUnderTest.accept( 25 | () -> { 26 | } 27 | ); 28 | assertThat(System.out).isSameAs(originalOut); 29 | } 30 | 31 | @Test 32 | void after_statement_throws_exception() { 33 | PrintStream originalOut = System.out; 34 | ignoreException( 35 | () -> methodUnderTest.accept( 36 | () -> { 37 | throw new Exception("some exception"); 38 | } 39 | ) 40 | ); 41 | assertThat(System.out).isSameAs(originalOut); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/RestoreSystemPropertiesTest.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import java.util.Properties; 4 | import java.util.concurrent.atomic.AtomicReference; 5 | 6 | import org.junit.jupiter.api.DisplayNameGeneration; 7 | import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; 8 | import org.junit.jupiter.api.Nested; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import static com.github.stefanbirkner.fishbowl.Fishbowl.ignoreException; 12 | import static com.github.stefanbirkner.systemlambda.SystemLambda.restoreSystemProperties; 13 | import static java.lang.System.clearProperty; 14 | import static java.lang.System.getProperty; 15 | import static java.lang.System.setProperty; 16 | import static org.assertj.core.api.Assertions.assertThat; 17 | 18 | @DisplayNameGeneration(ReplaceUnderscores.class) 19 | class RestoreSystemPropertiesTest { 20 | @Test 21 | void statement_is_executed( 22 | ) throws Exception { 23 | StatementMock statementMock = new StatementMock(); 24 | 25 | restoreSystemProperties( 26 | statementMock 27 | ); 28 | 29 | assertThat(statementMock.hasBeenEvaluated).isTrue(); 30 | } 31 | 32 | @Nested 33 | class properties_have_the_same_values_as_before { 34 | @Test 35 | void after_statement_is_executed( 36 | ) throws Exception { 37 | setProperty("some property", "some value"); 38 | setProperty("another property", "some value"); 39 | 40 | restoreSystemProperties( 41 | () -> { 42 | clearProperty("some property"); 43 | setProperty("another property", "another value"); 44 | } 45 | ); 46 | 47 | assertThat(getProperty("some property")) 48 | .isEqualTo("some value"); 49 | assertThat(getProperty("another property")) 50 | .isEqualTo("some value"); 51 | } 52 | 53 | @Test 54 | void after_statement_throws_exception( 55 | ) { 56 | setProperty("some property", "some value"); 57 | setProperty("another property", "some value"); 58 | 59 | ignoreException(() -> 60 | restoreSystemProperties( 61 | () -> { 62 | clearProperty("some property"); 63 | setProperty("another property", "another value"); 64 | throw new RuntimeException(); 65 | } 66 | ) 67 | ); 68 | 69 | assertThat(getProperty("some property")) 70 | .isEqualTo("some value"); 71 | assertThat(getProperty("another property")) 72 | .isEqualTo("some value"); 73 | } 74 | } 75 | 76 | @Nested 77 | class property_that_does_not_exist_before_the_statement_is_executed { 78 | @Test 79 | void does_not_exist_afterwards( 80 | ) throws Exception { 81 | clearProperty("some property"); 82 | 83 | restoreSystemProperties( 84 | () -> setProperty("some property", "some value") 85 | ); 86 | 87 | assertThat(getProperty("some property")) 88 | .isNull(); 89 | } 90 | 91 | @Test 92 | void does_not_exist_after_statement_throws_exception( 93 | ) { 94 | clearProperty("some property"); 95 | 96 | ignoreException(() -> 97 | restoreSystemProperties( 98 | () -> { 99 | setProperty("some property", "some value"); 100 | throw new RuntimeException(); 101 | } 102 | ) 103 | ); 104 | 105 | assertThat(getProperty("some property")) 106 | .isNull(); 107 | } 108 | } 109 | 110 | @Test 111 | void at_start_of_the_statement_execution_properties_are_equal_to_the_original_properties( 112 | ) throws Exception { 113 | AtomicReference propertiesAtStartOfExecution 114 | = new AtomicReference<>(); 115 | //ensure at least one property is set 116 | setProperty("some property", "some value"); 117 | Properties originalProperties = System.getProperties(); 118 | 119 | restoreSystemProperties( 120 | () -> propertiesAtStartOfExecution.set(System.getProperties()) 121 | ); 122 | 123 | assertThat(propertiesAtStartOfExecution.get()) 124 | .isEqualTo(originalProperties); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/SecurityManagerMock.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import java.io.FileDescriptor; 4 | import java.net.InetAddress; 5 | import java.security.Permission; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | class SecurityManagerMock extends SecurityManager { 10 | Object securityContext = new Object(); 11 | boolean inCheck = false; 12 | ThreadGroup threadGroup; 13 | boolean topLevelWindow = false; 14 | Object windowOfCheckTopLevelWindowCall; 15 | List invocations = new ArrayList<>(); 16 | 17 | @Override 18 | public void checkCreateClassLoader() { 19 | logMethodCall("checkCreateClassLoader"); 20 | super.checkCreateClassLoader(); 21 | } 22 | 23 | @Override 24 | public void checkAccess(Thread thread) { 25 | logMethodCall("checkAccess", Thread.class, thread); 26 | super.checkAccess(thread); 27 | } 28 | 29 | @Override 30 | public void checkAccess(ThreadGroup threadGroup) { 31 | logMethodCall("checkAccess", ThreadGroup.class, threadGroup); 32 | super.checkAccess(threadGroup); 33 | } 34 | 35 | @Override 36 | public void checkExit(int i) { 37 | logMethodCall("checkExit", int.class, i); 38 | super.checkExit(i); 39 | } 40 | 41 | @Override 42 | public void checkExec(String s) { 43 | logMethodCall("checkExec", String.class, s); 44 | super.checkExec(s); 45 | } 46 | 47 | @Override 48 | public void checkLink(String s) { 49 | logMethodCall("checkLink", String.class, s); 50 | super.checkLink(s); 51 | } 52 | 53 | @Override 54 | public void checkRead(FileDescriptor fileDescriptor) { 55 | logMethodCall("checkRead", FileDescriptor.class, fileDescriptor); 56 | super.checkRead(fileDescriptor); 57 | } 58 | 59 | @Override 60 | public void checkRead(String s) { 61 | logMethodCall("checkRead", String.class, s); 62 | super.checkRead(s); 63 | } 64 | 65 | @Override 66 | public void checkRead(String s, Object o) { 67 | logMethodCall( 68 | "checkRead", 69 | new Class[] { String.class, Object.class }, 70 | s, o); 71 | super.checkRead(s, o); 72 | } 73 | 74 | @Override 75 | public void checkWrite(FileDescriptor fileDescriptor) { 76 | logMethodCall("checkWrite", FileDescriptor.class, fileDescriptor); 77 | super.checkWrite(fileDescriptor); 78 | } 79 | 80 | @Override 81 | public void checkWrite(String s) { 82 | logMethodCall("checkWrite", String.class, s); 83 | super.checkWrite(s); 84 | } 85 | 86 | @Override 87 | public void checkDelete(String s) { 88 | logMethodCall("checkDelete", String.class, s); 89 | super.checkDelete(s); 90 | } 91 | 92 | @Override 93 | public void checkConnect(String s, int i) { 94 | logMethodCall( 95 | "checkConnect", 96 | new Class[] { String.class, int.class }, 97 | s, i); 98 | super.checkConnect(s, i); 99 | } 100 | 101 | @Override 102 | public void checkConnect(String s, int i, Object o) { 103 | logMethodCall( 104 | "checkConnect", 105 | new Class[] { String.class, int.class, Object.class }, 106 | s, i, o); 107 | super.checkConnect(s, i, o); 108 | } 109 | 110 | @Override 111 | public void checkListen(int i) { 112 | logMethodCall("checkListen", int.class, i); 113 | super.checkListen(i); 114 | } 115 | 116 | @Override 117 | public void checkAccept(String s, int i) { 118 | logMethodCall( 119 | "checkAccept", 120 | new Class[] { String.class, int.class }, 121 | s, i); 122 | super.checkAccept(s, i); 123 | } 124 | 125 | @Override 126 | public void checkMulticast(InetAddress inetAddress) { 127 | logMethodCall("checkMulticast", InetAddress.class, inetAddress); 128 | super.checkMulticast(inetAddress); 129 | } 130 | 131 | @Override 132 | public void checkMulticast(InetAddress inetAddress, byte b) { 133 | logMethodCall( 134 | "checkMulticast", 135 | new Class[] { InetAddress.class, byte.class }, 136 | inetAddress, b); 137 | super.checkMulticast(inetAddress, b); 138 | } 139 | 140 | @Override 141 | public void checkPropertiesAccess() { 142 | logMethodCall("checkPropertiesAccess"); 143 | super.checkPropertiesAccess(); 144 | } 145 | 146 | @Override 147 | public void checkPropertyAccess(String s) { 148 | logMethodCall("checkPropertyAccess", String.class, s); 149 | super.checkPropertyAccess(s); 150 | } 151 | 152 | @Override 153 | public void checkPrintJobAccess() { 154 | logMethodCall("checkPrintJobAccess"); 155 | super.checkPrintJobAccess(); 156 | } 157 | 158 | @Override 159 | public void checkSystemClipboardAccess() { 160 | logMethodCall("checkSystemClipboardAccess"); 161 | super.checkSystemClipboardAccess(); 162 | } 163 | 164 | @Override 165 | public void checkAwtEventQueueAccess() { 166 | logMethodCall("checkAwtEventQueueAccess"); 167 | super.checkAwtEventQueueAccess(); 168 | } 169 | 170 | @Override 171 | public void checkPackageAccess(String s) { 172 | logMethodCall("checkPackageAccess", String.class, s); 173 | super.checkPackageAccess(s); 174 | } 175 | 176 | @Override 177 | public void checkPackageDefinition(String s) { 178 | logMethodCall("checkPackageDefinition", String.class, s); 179 | super.checkPackageDefinition(s); 180 | } 181 | 182 | @Override 183 | public void checkPermission(Permission permission) { 184 | logMethodCall("checkPermission", Permission.class, permission); 185 | // everything is allowed 186 | } 187 | 188 | @Override 189 | public void checkPermission(Permission permission, Object o) { 190 | logMethodCall( 191 | "checkPermission", 192 | new Class[] { Permission.class, Object.class }, 193 | permission, o); 194 | // everything is allowed 195 | } 196 | 197 | @Override 198 | public void checkSetFactory() { 199 | logMethodCall("checkSetFactory"); 200 | super.checkSetFactory(); 201 | } 202 | 203 | @Override 204 | public void checkMemberAccess(Class aClass, int i) { 205 | logMethodCall( 206 | "checkMemberAccess", 207 | new Class[] { Class.class, int.class }, 208 | aClass, i); 209 | super.checkMemberAccess(aClass, i); 210 | } 211 | 212 | @Override 213 | public void checkSecurityAccess(String s) { 214 | logMethodCall("checkSecurityAccess", String.class, s); 215 | super.checkSecurityAccess(s); 216 | } 217 | 218 | @Override 219 | public Object getSecurityContext() { 220 | logMethodCall("getSecurityContext"); 221 | return securityContext; 222 | } 223 | 224 | @Override 225 | public boolean getInCheck() { 226 | logMethodCall("getInCheck"); 227 | return inCheck; 228 | } 229 | 230 | @Override 231 | public ThreadGroup getThreadGroup() { 232 | logMethodCall("getThreadGroup"); 233 | return threadGroup; 234 | } 235 | 236 | @Override 237 | public boolean checkTopLevelWindow(Object window) { 238 | logMethodCall("checkTopLevelWindow", Object.class, window); 239 | windowOfCheckTopLevelWindowCall = window; 240 | return topLevelWindow; 241 | } 242 | 243 | void logMethodCall(String name) { 244 | logMethodCall(name, new Class[0]); 245 | } 246 | 247 | void logMethodCall(String name, Class parameterType, Object argument) { 248 | logMethodCall(name, new Class[] { parameterType }, argument); 249 | } 250 | 251 | void logMethodCall(String name, Class[] parameterTypes, Object... arguments) { 252 | invocations.add( 253 | new Invocation( 254 | name, 255 | parameterTypes, 256 | arguments 257 | ) 258 | ); 259 | } 260 | 261 | static class Invocation { 262 | final String methodName; 263 | final Class[] parameterTypes; 264 | final Object[] arguments; 265 | 266 | Invocation( 267 | String methodName, 268 | Class[] parameterTypes, 269 | Object[] arguments 270 | ) { 271 | this.methodName = methodName; 272 | this.parameterTypes = parameterTypes; 273 | this.arguments = arguments; 274 | } 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/StatementMock.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | class StatementMock implements Statement { 4 | boolean hasBeenEvaluated = false; 5 | 6 | @Override 7 | public void execute() throws Exception { 8 | hasBeenEvaluated = true; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/TapSystemErrAndOutNormalizedTest.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import org.junit.jupiter.api.DisplayNameGeneration; 4 | import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; 5 | import org.junit.jupiter.api.Nested; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static com.github.stefanbirkner.systemlambda.SystemLambda.*; 9 | import static java.lang.System.err; 10 | import static java.lang.System.out; 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | @DisplayNameGeneration(ReplaceUnderscores.class) 14 | class TapSystemErrAndOutNormalizedTest { 15 | 16 | @Test 17 | void taps_text_that_is_written_to_System_err_and_out_by_statement_has_only_slash_n_for_new_line( 18 | ) throws Exception { 19 | String textWrittenToSystemErr = tapSystemErrAndOutNormalized( 20 | () -> { 21 | err.println("line 1"); 22 | out.println("line 2"); 23 | err.println("line 3"); 24 | out.println("line 4"); 25 | } 26 | ); 27 | 28 | assertThat(textWrittenToSystemErr) 29 | .isEqualTo("line 1\nline 2\nline 3\nline 4\n"); 30 | } 31 | 32 | @Test 33 | void tapped_text_is_empty_when_statement_does_not_write_to_System_err_nor_out( 34 | ) throws Exception { 35 | String textWrittenToSystemErr = tapSystemErrAndOutNormalized( 36 | () -> {} 37 | ); 38 | 39 | assertThat(textWrittenToSystemErr) 40 | .isEqualTo(""); 41 | } 42 | 43 | @Nested 44 | class System_err_is_same_as_before 45 | extends RestoreSystemErrChecks 46 | { 47 | System_err_is_same_as_before() { 48 | super(SystemLambda::tapSystemErrAndOutNormalized); 49 | } 50 | } 51 | 52 | @Nested 53 | class System_out_is_same_as_before 54 | extends RestoreSystemOutChecks 55 | { 56 | System_out_is_same_as_before() { 57 | super(SystemLambda::tapSystemErrAndOutNormalized); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/TapSystemErrAndOutTest.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import org.junit.jupiter.api.DisplayNameGeneration; 4 | import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; 5 | import org.junit.jupiter.api.Nested; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemErr; 9 | import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemErrAndOut; 10 | import static java.lang.System.err; 11 | import static java.lang.System.out; 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | @DisplayNameGeneration(ReplaceUnderscores.class) 15 | class TapSystemErrAndOutTest { 16 | 17 | @Test 18 | void taps_text_that_is_written_to_System_err_and_out_by_statement( 19 | ) throws Exception { 20 | String textWrittenToSystemErrAndOut = tapSystemErrAndOut( 21 | () -> { 22 | err.print("word1 "); 23 | out.print("word2 "); 24 | err.print("word3 "); 25 | out.print("word4 "); 26 | } 27 | ); 28 | 29 | assertThat(textWrittenToSystemErrAndOut) 30 | .isEqualTo("word1 word2 word3 word4 "); 31 | } 32 | 33 | @Test 34 | void tapped_text_is_empty_when_statement_does_not_write_to_System_err_nor_out( 35 | ) throws Exception { 36 | String textWrittenToSystemErrAndOut = tapSystemErrAndOut( 37 | () -> {} 38 | ); 39 | 40 | assertThat(textWrittenToSystemErrAndOut) 41 | .isEqualTo(""); 42 | } 43 | 44 | @Nested 45 | class System_err_is_same_as_before 46 | extends RestoreSystemErrChecks 47 | { 48 | System_err_is_same_as_before() { 49 | super(SystemLambda::tapSystemErrAndOut); 50 | } 51 | } 52 | 53 | @Nested 54 | class System_out_is_same_as_before 55 | extends RestoreSystemOutChecks 56 | { 57 | System_out_is_same_as_before() { 58 | super(SystemLambda::tapSystemErrAndOut); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/TapSystemErrNormalizedTest.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import org.junit.jupiter.api.DisplayNameGeneration; 4 | import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; 5 | import org.junit.jupiter.api.Nested; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemErrNormalized; 9 | import static java.lang.System.err; 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | @DisplayNameGeneration(ReplaceUnderscores.class) 13 | class TapSystemErrNormalizedTest { 14 | 15 | @Test 16 | void taps_text_that_is_written_to_System_err_by_statement_has_only_slash_n_for_new_line( 17 | ) throws Exception { 18 | String textWrittenToSystemErr = tapSystemErrNormalized( 19 | () -> err.println("some text") 20 | ); 21 | 22 | assertThat(textWrittenToSystemErr) 23 | .isEqualTo("some text\n"); 24 | } 25 | 26 | @Test 27 | void tapped_text_is_empty_when_statement_does_not_write_to_System_err( 28 | ) throws Exception { 29 | String textWrittenToSystemErr = tapSystemErrNormalized( 30 | () -> {} 31 | ); 32 | 33 | assertThat(textWrittenToSystemErr) 34 | .isEqualTo(""); 35 | } 36 | 37 | @Nested 38 | class System_err_is_same_as_before 39 | extends RestoreSystemErrChecks 40 | { 41 | System_err_is_same_as_before() { 42 | super(SystemLambda::tapSystemErrNormalized); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/TapSystemErrTest.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import org.junit.jupiter.api.DisplayNameGeneration; 4 | import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; 5 | import org.junit.jupiter.api.Nested; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemErr; 9 | import static java.lang.System.err; 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | @DisplayNameGeneration(ReplaceUnderscores.class) 13 | class TapSystemErrTest { 14 | 15 | @Test 16 | void taps_text_that_is_written_to_System_err_by_statement( 17 | ) throws Exception { 18 | String textWrittenToSystemErr = tapSystemErr( 19 | () -> err.print("some text") 20 | ); 21 | 22 | assertThat(textWrittenToSystemErr) 23 | .isEqualTo("some text"); 24 | } 25 | 26 | @Test 27 | void tapped_text_is_empty_when_statement_does_not_write_to_System_err( 28 | ) throws Exception { 29 | String textWrittenToSystemErr = tapSystemErr( 30 | () -> {} 31 | ); 32 | 33 | assertThat(textWrittenToSystemErr) 34 | .isEqualTo(""); 35 | } 36 | 37 | @Nested 38 | class System_err_is_same_as_before 39 | extends RestoreSystemErrChecks 40 | { 41 | System_err_is_same_as_before() { 42 | super(SystemLambda::tapSystemErr); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/TapSystemOutNormalizedTest.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import org.junit.jupiter.api.DisplayNameGeneration; 4 | import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; 5 | import org.junit.jupiter.api.Nested; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemOutNormalized; 9 | import static java.lang.System.out; 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | @DisplayNameGeneration(ReplaceUnderscores.class) 13 | class TapSystemOutNormalizedTest { 14 | 15 | @Test 16 | void taps_text_that_is_written_to_System_out_by_statement_has_only_slash_n_for_new_line( 17 | ) throws Exception { 18 | String textWrittenToSystemOut = tapSystemOutNormalized( 19 | () -> out.println("some text") 20 | ); 21 | 22 | assertThat(textWrittenToSystemOut) 23 | .isEqualTo("some text\n"); 24 | } 25 | 26 | @Test 27 | void tapped_text_is_empty_when_statement_does_not_write_to_System_out( 28 | ) throws Exception { 29 | String textWrittenToSystemOut = tapSystemOutNormalized( 30 | () -> {} 31 | ); 32 | 33 | assertThat(textWrittenToSystemOut) 34 | .isEqualTo(""); 35 | } 36 | 37 | @Nested 38 | class System_out_is_same_as_before 39 | extends RestoreSystemOutChecks 40 | { 41 | System_out_is_same_as_before() { 42 | super(SystemLambda::tapSystemOutNormalized); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/TapSystemOutTest.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import org.junit.jupiter.api.DisplayNameGeneration; 4 | import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; 5 | import org.junit.jupiter.api.Nested; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemOut; 9 | import static java.lang.System.*; 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | @DisplayNameGeneration(ReplaceUnderscores.class) 13 | class TapSystemOutTest { 14 | 15 | @Test 16 | void taps_text_that_is_written_to_System_out_by_statement( 17 | ) throws Exception { 18 | String textWrittenToSystemOut = tapSystemOut( 19 | () -> out.print("some text") 20 | ); 21 | 22 | assertThat(textWrittenToSystemOut) 23 | .isEqualTo("some text"); 24 | } 25 | 26 | @Test 27 | void tapped_text_is_empty_when_statement_does_not_write_to_System_out( 28 | ) throws Exception { 29 | String textWrittenToSystemOut = tapSystemOut( 30 | () -> {} 31 | ); 32 | 33 | assertThat(textWrittenToSystemOut) 34 | .isEqualTo(""); 35 | } 36 | 37 | @Nested 38 | class System_out_is_same_as_before 39 | extends RestoreSystemOutChecks 40 | { 41 | System_out_is_same_as_before() { 42 | super(SystemLambda::tapSystemOut); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/WithEnvironmentVariableTest.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import com.github.stefanbirkner.systemlambda.SystemLambda.WithEnvironmentVariables; 4 | import org.apache.commons.lang3.RandomStringUtils; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.DisplayNameGeneration; 7 | import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; 8 | import org.junit.jupiter.api.Nested; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import java.lang.reflect.Field; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | import java.util.concurrent.Callable; 15 | 16 | import static com.github.stefanbirkner.fishbowl.Fishbowl.ignoreException; 17 | import static com.github.stefanbirkner.systemlambda.SystemLambda.withEnvironmentVariable; 18 | import static java.lang.Integer.parseInt; 19 | import static java.lang.System.getenv; 20 | import static org.apache.commons.lang3.StringUtils.substringBefore; 21 | import static org.assertj.core.api.Assertions.assertThat; 22 | import static org.assertj.core.api.Assertions.catchThrowable; 23 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 24 | 25 | @DisplayNameGeneration(ReplaceUnderscores.class) 26 | class WithEnvironmentVariableTest { 27 | 28 | @BeforeEach 29 | void skipForNewerJavaVersionsWhenEnvironmentVariablesMapCannotBeAccessed( 30 | ) throws Exception { 31 | assumeTrue( 32 | javaIsVersion15OrOlder() 33 | || internalEnvironmentsVariableMapIsAccessible() 34 | ); 35 | } 36 | 37 | @Test 38 | void callable_is_executed( 39 | ) throws Exception { 40 | CallableMock callable = new CallableMock(); 41 | withEnvironmentVariable("dummy name", "dummy value") 42 | .execute(callable); 43 | 44 | assertThat(callable.hasBeenEvaluated).isTrue(); 45 | } 46 | 47 | @Test 48 | void return_value_of_callable_is_exposed( 49 | ) throws Exception { 50 | String value = withEnvironmentVariable("dummy name", "dummy value") 51 | .execute(() -> "return value"); 52 | 53 | assertThat(value).isEqualTo("return value"); 54 | } 55 | 56 | @Test 57 | void statement_is_executed( 58 | ) throws Exception { 59 | StatementMock statementMock = new StatementMock(); 60 | 61 | withEnvironmentVariable("dummy name", "dummy value") 62 | .execute(statementMock); 63 | 64 | assertThat(statementMock.hasBeenEvaluated).isTrue(); 65 | } 66 | 67 | @Nested 68 | class environment_variable_that_is_set_to_some_value { 69 | @Test 70 | void is_available_in_the_callable( 71 | ) throws Exception { 72 | withEnvironmentVariable("dummy name", "dummy value") 73 | .execute(() -> { 74 | assertThat(getenv("dummy name")).isEqualTo("dummy value"); 75 | return "dummy return value"; 76 | }); 77 | } 78 | 79 | @Test 80 | void is_available_in_the_statement( 81 | ) throws Exception { 82 | withEnvironmentVariable("dummy name", "dummy value") 83 | .execute(() -> 84 | assertThat(getenv("dummy name")).isEqualTo("dummy value") 85 | ); 86 | } 87 | 88 | @Test 89 | void is_available_in_the_callable_from_environment_variables_map( 90 | ) throws Exception { 91 | withEnvironmentVariable("dummy name", "dummy value") 92 | .execute(() -> { 93 | assertThat(getenv()).containsEntry("dummy name", "dummy value"); 94 | return "dummy return value"; 95 | }); 96 | } 97 | 98 | @Test 99 | void is_available_in_the_statement_from_environment_variables_map( 100 | ) throws Exception { 101 | withEnvironmentVariable("dummy name", "dummy value") 102 | .execute(() -> 103 | assertThat(getenv()).containsEntry("dummy name", "dummy value") 104 | ); 105 | } 106 | } 107 | 108 | @Nested 109 | class multiple_environment_variable_that_are_set_to_some_value { 110 | @Test 111 | void are_available_in_the_callable( 112 | ) throws Exception { 113 | withEnvironmentVariable("first", "first value") 114 | .and("second", "second value") 115 | .execute(() -> { 116 | assertThat(getenv("first")).isEqualTo("first value"); 117 | assertThat(getenv("second")).isEqualTo("second value"); 118 | return "dummy return value"; 119 | }); 120 | } 121 | 122 | @Test 123 | void are_available_in_the_statement( 124 | ) throws Exception { 125 | withEnvironmentVariable("first", "first value") 126 | .and("second", "second value") 127 | .execute(() -> { 128 | assertThat(getenv("first")).isEqualTo("first value"); 129 | assertThat(getenv("second")).isEqualTo("second value"); 130 | }); 131 | } 132 | } 133 | 134 | @Nested 135 | class environment_variable_that_is_set_to_null { 136 | @Test 137 | void is_null_in_the_callable( 138 | ) throws Exception { 139 | //we need to set a value because it is null by default 140 | withEnvironmentVariable("dummy name", randomValue()) 141 | .execute(() -> 142 | withEnvironmentVariable("dummy name", null) 143 | .execute(() -> { 144 | assertThat(getenv("dummy name")).isNull(); 145 | return "dummy return value"; 146 | }) 147 | ); 148 | } 149 | 150 | @Test 151 | void is_null_in_the_statement( 152 | ) throws Exception { 153 | //we need to set a value because it is null by default 154 | withEnvironmentVariable("dummy name", randomValue()) 155 | .execute(() -> 156 | withEnvironmentVariable("dummy name", null) 157 | .execute(() -> assertThat(getenv("dummy name")).isNull()) 158 | ); 159 | } 160 | 161 | @Test 162 | void is_not_stored_in_the_environment_variables_map_in_the_callable( 163 | ) throws Exception { 164 | //we need to set a value because it is null by default 165 | withEnvironmentVariable("dummy name", randomValue()) 166 | .execute(() -> 167 | withEnvironmentVariable("dummy name", null) 168 | .execute(() -> { 169 | assertThat(getenv()).doesNotContainKey("dummy name"); 170 | return "dummy return value"; 171 | }) 172 | ); 173 | } 174 | 175 | @Test 176 | void is_not_stored_in_the_environment_variables_map_in_the_statement( 177 | ) throws Exception { 178 | //we need to set a value because it is null by default 179 | withEnvironmentVariable("dummy name", randomValue()) 180 | .execute(() -> 181 | withEnvironmentVariable("dummy name", null) 182 | .execute(() -> assertThat(getenv()).doesNotContainKey("dummy name")) 183 | ); 184 | } 185 | } 186 | 187 | @Test 188 | void an_environment_variable_cannot_be_set_twice() { 189 | Throwable exception = catchThrowable(() -> 190 | withEnvironmentVariable("dummy name", "first value") 191 | .and("dummy name", "second value") 192 | .execute(() -> {}) 193 | ); 194 | 195 | assertThat(exception) 196 | .isInstanceOf(IllegalArgumentException.class) 197 | .hasMessage( 198 | "The environment variable 'dummy name' cannot be set to" 199 | + " 'second value' because it was already set to 'first" 200 | + " value'." 201 | ); 202 | } 203 | 204 | @Test 205 | void when_an_environment_variable_is_set_twice_null_is_not_enclosed_in_single_quotes() { 206 | Throwable exception = catchThrowable(() -> 207 | withEnvironmentVariable("dummy name", null) 208 | .and("dummy name", null) 209 | .execute(() -> {}) 210 | ); 211 | 212 | assertThat(exception) 213 | .isInstanceOf(IllegalArgumentException.class) 214 | .hasMessage( 215 | "The environment variable 'dummy name' cannot be set to null" 216 | + " because it was already set to null." 217 | ); 218 | } 219 | 220 | @Test 221 | void the_and_method_creates_a_new_object_so_that_EnvironmentVariables_object_can_be_reused( 222 | ) throws Exception { 223 | WithEnvironmentVariables baseSetting = withEnvironmentVariable("first", "first value"); 224 | baseSetting.and("second", "second value") 225 | .execute(() -> {}); 226 | 227 | baseSetting.and("third", "third value") 228 | .execute(() -> { 229 | assertThat(getenv("first")).isEqualTo("first value"); 230 | assertThat(getenv("second")).isNull(); 231 | assertThat(getenv("third")).isEqualTo("third value"); 232 | }); 233 | } 234 | 235 | @Nested 236 | class environment_variables_map_contains_same_values_as_before { 237 | @Test 238 | void after_callable_is_executed( 239 | ) throws Exception { 240 | Map originalEnvironmentVariables 241 | = new HashMap<>(getenv()); 242 | 243 | withEnvironmentVariable("dummy name", randomValue()) 244 | .execute(() -> "dummy return value"); 245 | 246 | assertThat(getenv()).isEqualTo(originalEnvironmentVariables); 247 | } 248 | 249 | @Test 250 | void after_statement_is_executed( 251 | ) throws Exception { 252 | Map originalEnvironmentVariables 253 | = new HashMap<>(getenv()); 254 | 255 | withEnvironmentVariable("dummy name", randomValue()) 256 | .execute(() -> { 257 | }); 258 | 259 | assertThat(getenv()).isEqualTo(originalEnvironmentVariables); 260 | } 261 | 262 | @Test 263 | void after_callable_throws_exception() { 264 | Map originalEnvironmentVariables 265 | = new HashMap<>(getenv()); 266 | 267 | ignoreException( 268 | () -> withEnvironmentVariable("dummy name", randomValue()) 269 | .execute((Callable) () -> { 270 | throw new RuntimeException("dummy exception"); 271 | }) 272 | ); 273 | 274 | assertThat(getenv()).isEqualTo(originalEnvironmentVariables); 275 | } 276 | 277 | @Test 278 | void after_statement_throws_exception() { 279 | Map originalEnvironmentVariables 280 | = new HashMap<>(getenv()); 281 | 282 | ignoreException( 283 | () -> withEnvironmentVariable("dummy name", randomValue()) 284 | .execute(() -> { 285 | throw new RuntimeException("dummy exception"); } 286 | ) 287 | ); 288 | 289 | assertThat(getenv()).isEqualTo(originalEnvironmentVariables); 290 | } 291 | } 292 | 293 | @Nested 294 | class environment_variables_are_the_same_as_before { 295 | @Test 296 | void after_callable_is_executed( 297 | ) throws Exception { 298 | String originalValue = getenv("dummy name"); 299 | 300 | withEnvironmentVariable("dummy name", randomValue()) 301 | .execute(() -> "dummy return value"); 302 | 303 | assertThat(getenv("dummy name")).isEqualTo(originalValue); 304 | } 305 | 306 | @Test 307 | void after_statement_is_executed( 308 | ) throws Exception { 309 | String originalValue = getenv("dummy name"); 310 | 311 | withEnvironmentVariable("dummy name", randomValue()) 312 | .execute(() -> { 313 | }); 314 | 315 | assertThat(getenv("dummy name")).isEqualTo(originalValue); 316 | } 317 | 318 | @Test 319 | void after_callable_throws_exception() { 320 | String originalValue = getenv("dummy name"); 321 | 322 | ignoreException( 323 | () -> withEnvironmentVariable("dummy name", randomValue()) 324 | .execute((Callable) () -> { 325 | throw new RuntimeException("dummy exception"); 326 | }) 327 | ); 328 | 329 | assertThat(getenv("dummy name")).isEqualTo(originalValue); 330 | } 331 | 332 | @Test 333 | void after_statement_throws_exception() { 334 | String originalValue = getenv("dummy name"); 335 | 336 | ignoreException( 337 | () -> withEnvironmentVariable("dummy name", randomValue()) 338 | .execute(() -> { 339 | throw new RuntimeException("dummy exception"); 340 | } 341 | ) 342 | ); 343 | 344 | assertThat(getenv("dummy name")).isEqualTo(originalValue); 345 | } 346 | } 347 | 348 | private boolean javaIsVersion15OrOlder() { 349 | String javaVersion = System.getProperty("java.version"); 350 | String majorVersion = substringBefore(javaVersion, "."); 351 | return parseInt(majorVersion) <= 15; 352 | } 353 | 354 | private boolean internalEnvironmentsVariableMapIsAccessible( 355 | ) throws NoSuchFieldException { 356 | try { 357 | Field field = getenv().getClass().getDeclaredField("m"); 358 | field.setAccessible(true); //check whether it can be made accessible 359 | field.setAccessible(false); //revert change 360 | return true; 361 | } catch (Exception e) { 362 | // InaccessibleObjectException cannot be explicitly caught because 363 | // it does not exist in Java 8. 364 | if (e.getClass().getName() 365 | .equals("java.lang.reflect.InaccessibleObjectException") 366 | ) { 367 | return false; 368 | } else { 369 | throw e; 370 | } 371 | } 372 | } 373 | 374 | private String randomValue() { 375 | return RandomStringUtils.random(20); 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/WithSecurityManagerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import org.junit.jupiter.api.DisplayNameGeneration; 4 | import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; 5 | import org.junit.jupiter.api.Nested; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static com.github.stefanbirkner.fishbowl.Fishbowl.ignoreException; 9 | import static com.github.stefanbirkner.systemlambda.SystemLambda.withSecurityManager; 10 | import static java.lang.System.getSecurityManager; 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | @DisplayNameGeneration(ReplaceUnderscores.class) 14 | class WithSecurityManagerTest { 15 | private static final SecurityManager MANAGER = new SecurityManagerMock(); 16 | 17 | @Test 18 | void specified_security_manager_is_present_while_statement_is_executed( 19 | ) throws Exception { 20 | withSecurityManager( 21 | MANAGER, 22 | () -> assertThat(getSecurityManager()).isSameAs(MANAGER) 23 | ); 24 | } 25 | 26 | @Test 27 | void statement_is_executed() throws Exception { 28 | StatementMock statementMock = new StatementMock(); 29 | 30 | withSecurityManager( 31 | MANAGER, 32 | statementMock 33 | ); 34 | 35 | assertThat(statementMock.hasBeenEvaluated).isTrue(); 36 | } 37 | 38 | @Nested 39 | class the_security_manager_is_the_same_as_before { 40 | @Test 41 | void after_statement_is_executed( 42 | ) throws Exception { 43 | SecurityManager originalManager = getSecurityManager(); 44 | 45 | withSecurityManager( 46 | MANAGER, 47 | () -> { 48 | } 49 | ); 50 | 51 | assertThat(getSecurityManager()).isSameAs(originalManager); 52 | } 53 | 54 | @Test 55 | void after_statement_throws_exception() { 56 | SecurityManager originalSecurityManager = getSecurityManager(); 57 | 58 | ignoreException(() -> 59 | withSecurityManager( 60 | MANAGER, 61 | () -> { 62 | throw new RuntimeException(); 63 | } 64 | ) 65 | ); 66 | 67 | assertThat(getSecurityManager()).isSameAs(originalSecurityManager); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/com/github/stefanbirkner/systemlambda/WithTextFromSystemInTest.java: -------------------------------------------------------------------------------- 1 | package com.github.stefanbirkner.systemlambda; 2 | 3 | import org.junit.jupiter.api.*; 4 | import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.util.Scanner; 9 | import java.util.concurrent.atomic.AtomicInteger; 10 | import java.util.concurrent.atomic.AtomicReference; 11 | 12 | import static com.github.stefanbirkner.fishbowl.Fishbowl.exceptionThrownBy; 13 | import static com.github.stefanbirkner.systemlambda.SystemLambda.withTextFromSystemIn; 14 | import static java.lang.String.format; 15 | import static java.lang.System.in; 16 | import static java.util.concurrent.TimeUnit.SECONDS; 17 | import static org.assertj.core.api.Assertions.assertThat; 18 | import static org.assertj.core.api.Assertions.catchThrowable; 19 | 20 | @DisplayNameGeneration(ReplaceUnderscores.class) 21 | @Timeout(value = 10, unit = SECONDS) 22 | class WithTextFromSystemInTest { 23 | private static final byte[] DUMMY_ARRAY = new byte[1024]; 24 | private static final int VALID_OFFSET = 2; 25 | private static final int VALID_READ_LENGTH = 100; 26 | private static final IOException DUMMY_IO_EXCEPTION = new IOException(); 27 | private static final RuntimeException DUMMY_RUNTIME_EXCEPTION 28 | = new RuntimeException(); 29 | 30 | @BeforeAll 31 | static void checkArrayConstants() { 32 | assertThat(VALID_OFFSET).isBetween(0, DUMMY_ARRAY.length); 33 | assertThat(VALID_READ_LENGTH) 34 | .isBetween(0, DUMMY_ARRAY.length - VALID_OFFSET); 35 | } 36 | 37 | @Test 38 | void provided_text_is_available_from_system_in( 39 | ) throws Exception { 40 | AtomicReference secondLineCapture = new AtomicReference<>(); 41 | 42 | withTextFromSystemIn( 43 | "first line", 44 | "second line" 45 | ).execute(() -> { 46 | Scanner firstScanner = new Scanner(in); 47 | firstScanner.nextLine(); 48 | Scanner secondScanner = new Scanner(in); 49 | secondLineCapture.set(secondScanner.nextLine()); 50 | }); 51 | 52 | assertThat(secondLineCapture).hasValue("second line"); 53 | } 54 | 55 | @Test 56 | void no_text_is_available_from_system_in_if_no_text_has_been_provided( 57 | ) throws Exception { 58 | AtomicInteger charCapture = new AtomicInteger(); 59 | 60 | withTextFromSystemIn() 61 | .execute(() -> { 62 | charCapture.set(in.read()); 63 | }); 64 | 65 | assertThat(charCapture).hasValue(-1); 66 | } 67 | 68 | @Test 69 | void system_in_provides_specified_text_and_throws_requested_IOException_afterwards( 70 | ) throws Exception { 71 | withTextFromSystemIn("arbitrary text") 72 | .andExceptionThrownOnInputEnd(DUMMY_IO_EXCEPTION) 73 | .execute(() -> { 74 | assertSystemInProvidesText(format("arbitrary text%n")); 75 | Throwable exception = exceptionThrownBy(in::read); 76 | assertThat(exception).isSameAs(DUMMY_IO_EXCEPTION); 77 | }); 78 | } 79 | 80 | @Test 81 | void system_in_throws_requested_IOException_on_first_read_if_no_text_has_been_specified( 82 | ) throws Exception { 83 | withTextFromSystemIn() 84 | .andExceptionThrownOnInputEnd(DUMMY_IO_EXCEPTION) 85 | .execute(() -> { 86 | Throwable exception = exceptionThrownBy(in::read); 87 | assertThat(exception).isSameAs(DUMMY_IO_EXCEPTION); 88 | }); 89 | } 90 | 91 | @Test 92 | void system_in_provides_specified_text_and_throws_requested_RuntimeException_afterwards( 93 | ) throws Exception { 94 | withTextFromSystemIn("arbitrary text") 95 | .andExceptionThrownOnInputEnd(DUMMY_RUNTIME_EXCEPTION) 96 | .execute(() -> { 97 | assertSystemInProvidesText(format("arbitrary text%n")); 98 | Throwable exception = exceptionThrownBy(in::read); 99 | assertThat(exception).isSameAs(DUMMY_RUNTIME_EXCEPTION); 100 | }); 101 | } 102 | 103 | @Test 104 | void system_in_throws_requested_RuntimeException_on_first_read_if_no_text_has_been_specified( 105 | ) throws Exception { 106 | withTextFromSystemIn() 107 | .andExceptionThrownOnInputEnd(DUMMY_RUNTIME_EXCEPTION) 108 | .execute(() -> { 109 | Throwable exception = exceptionThrownBy(in::read); 110 | assertThat(exception).isSameAs(DUMMY_RUNTIME_EXCEPTION); 111 | }); 112 | } 113 | 114 | @Test 115 | void an_IOException_cannot_be_requested_if_a_RuntimeException_has_already_been_requested() { 116 | Throwable exception = exceptionThrownBy(() -> { 117 | withTextFromSystemIn() 118 | .andExceptionThrownOnInputEnd(DUMMY_RUNTIME_EXCEPTION) 119 | .andExceptionThrownOnInputEnd(DUMMY_IO_EXCEPTION) 120 | .execute(() -> {}); 121 | }); 122 | 123 | assertThat(exception) 124 | .hasMessage( 125 | "You cannot call andExceptionThrownOnInputEnd(IOException)" 126 | + " because andExceptionThrownOnInputEnd(RuntimeException) has" 127 | + " already been called."); 128 | } 129 | 130 | @Test 131 | void a_RuntimeException_cannot_be_requested_if_an_IOException_has_already_been_requested() { 132 | Throwable exception = exceptionThrownBy(() -> { 133 | withTextFromSystemIn() 134 | .andExceptionThrownOnInputEnd(DUMMY_IO_EXCEPTION) 135 | .andExceptionThrownOnInputEnd(DUMMY_RUNTIME_EXCEPTION) 136 | .execute(() -> {}); 137 | }); 138 | 139 | assertThat(exception) 140 | .hasMessage( 141 | "You cannot call andExceptionThrownOnInputEnd(RuntimeException)" 142 | + " because andExceptionThrownOnInputEnd(IOException) has" 143 | + " already been called."); 144 | } 145 | 146 | //this is default behaviour of an InputStream according to its JavaDoc 147 | @Test 148 | void system_in_throws_NullPointerException_when_read_is_called_with_null_array( 149 | ) throws Exception { 150 | withTextFromSystemIn("arbitrary text") 151 | .execute(() -> { 152 | Throwable exception = catchThrowable( 153 | () -> in.read(null) 154 | ); 155 | assertThat(exception) 156 | .isInstanceOf(NullPointerException.class); 157 | }); 158 | } 159 | 160 | //this is default behaviour of an InputStream according to its JavaDoc 161 | @Test 162 | void system_in_throws_IndexOutOfBoundsException_when_read_is_called_with_negative_offset( 163 | ) throws Exception { 164 | withTextFromSystemIn("arbitrary text") 165 | .execute(() -> { 166 | Throwable exception = catchThrowable( 167 | () -> System.in.read(DUMMY_ARRAY, -1, VALID_READ_LENGTH) 168 | ); 169 | assertThat(exception) 170 | .isInstanceOf(IndexOutOfBoundsException.class); 171 | }); 172 | } 173 | 174 | //this is default behaviour of an InputStream according to its JavaDoc 175 | @Test 176 | void system_in_throws_IndexOutOfBoundsException_when_read_is_called_with_negative_length( 177 | ) throws Exception { 178 | withTextFromSystemIn("arbitrary text") 179 | .execute(() -> { 180 | Throwable exception = catchThrowable( 181 | () -> System.in.read(DUMMY_ARRAY, VALID_OFFSET, -1) 182 | ); 183 | assertThat(exception) 184 | .isInstanceOf(IndexOutOfBoundsException.class); 185 | }); 186 | } 187 | 188 | //this is default behaviour of an InputStream according to its JavaDoc 189 | @Test 190 | void system_in_throws_IndexOutOfBoundsException_when_read_is_called_with_oversized_length( 191 | ) throws Exception { 192 | withTextFromSystemIn("arbitrary text") 193 | .execute(() -> { 194 | Throwable exception = catchThrowable(() -> { 195 | int oversizedLength = DUMMY_ARRAY.length - VALID_OFFSET + 1; 196 | System.in.read(DUMMY_ARRAY, VALID_OFFSET, oversizedLength); 197 | }); 198 | assertThat(exception) 199 | .isInstanceOf(IndexOutOfBoundsException.class); 200 | }); 201 | } 202 | 203 | @Test 204 | void system_in_reads_zero_bytes_even_if_mock_should_throw_IOException_on_input_end( 205 | ) throws Exception { 206 | withTextFromSystemIn() 207 | .andExceptionThrownOnInputEnd(DUMMY_IO_EXCEPTION) 208 | .execute(() -> { 209 | int numBytesRead = System.in.read(DUMMY_ARRAY, VALID_OFFSET, 0); 210 | assertThat(numBytesRead).isZero(); 211 | }); 212 | } 213 | 214 | @Test 215 | void system_in_reads_zero_bytes_even_if_mock_should_throw_RuntimeException_on_input_end( 216 | ) throws Exception { 217 | withTextFromSystemIn() 218 | .andExceptionThrownOnInputEnd(DUMMY_RUNTIME_EXCEPTION) 219 | .execute(() -> { 220 | int numBytesRead = System.in.read(DUMMY_ARRAY, VALID_OFFSET, 0); 221 | assertThat(numBytesRead).isZero(); 222 | }); 223 | } 224 | 225 | @Test 226 | void system_in_read_bytes_throws_specified_IOException_on_input_end( 227 | ) throws Exception { 228 | withTextFromSystemIn() 229 | .andExceptionThrownOnInputEnd(DUMMY_IO_EXCEPTION) 230 | .execute(() -> { 231 | Throwable exception = catchThrowable( 232 | () -> System.in.read( 233 | DUMMY_ARRAY, VALID_OFFSET, VALID_READ_LENGTH 234 | ) 235 | ); 236 | assertThat(exception) 237 | .isSameAs(DUMMY_IO_EXCEPTION); 238 | }); 239 | } 240 | 241 | @Test 242 | void system_in_read_bytes_throws_specified_RuntimeException_on_input_end( 243 | ) throws Exception { 244 | AtomicReference exceptionCapture = new AtomicReference<>(); 245 | 246 | withTextFromSystemIn() 247 | .andExceptionThrownOnInputEnd(DUMMY_RUNTIME_EXCEPTION) 248 | .execute(() -> { 249 | Throwable exception = catchThrowable( 250 | () -> System.in.read( 251 | DUMMY_ARRAY, VALID_OFFSET, VALID_READ_LENGTH 252 | ) 253 | ); 254 | exceptionCapture.set(exception); 255 | }); 256 | 257 | assertThat(exceptionCapture) 258 | .hasValue(DUMMY_RUNTIME_EXCEPTION); 259 | } 260 | 261 | 262 | @Nested 263 | class System_in_is_same_as_before { 264 | @Test 265 | void after_statement_is_executed( 266 | ) throws Exception { 267 | InputStream originalSystemIn = System.in; 268 | 269 | withTextFromSystemIn("arbitrary text") 270 | .execute(() -> {}); 271 | 272 | assertThat(System.in).isSameAs(originalSystemIn); 273 | } 274 | 275 | @Test 276 | void after_statement_throws_exception( 277 | ) { 278 | InputStream originalSystemIn = System.in; 279 | 280 | catchThrowable( 281 | () -> withTextFromSystemIn("arbitrary text") 282 | .execute( 283 | () -> { 284 | throw new Exception("some exception"); 285 | } 286 | ) 287 | ); 288 | 289 | assertThat(System.in).isSameAs(originalSystemIn); 290 | } 291 | } 292 | 293 | private static void assertSystemInProvidesText( 294 | String text 295 | ) throws IOException { 296 | for (char c : text.toCharArray()) 297 | assertThat((char) System.in.read()).isSameAs(c); 298 | } 299 | } 300 | 301 | --------------------------------------------------------------------------------