├── .editorconfig
├── .github
├── dependabot.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── LICENSE
├── README.md
├── RELEASE.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── logcapture-core
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── org
│ │ └── logcapture
│ │ ├── LogCapture.java
│ │ ├── assertion
│ │ ├── ExpectedLoggedException.java
│ │ ├── ExpectedLoggingMessage.java
│ │ └── VerificationException.java
│ │ ├── logback
│ │ └── StubAppender.java
│ │ └── matcher
│ │ ├── ExpectedExceptionMatcher.java
│ │ ├── TypedAnythingMatcher.java
│ │ └── exception
│ │ ├── ExceptionCauseMatcher.java
│ │ └── ExceptionCauseMessageMatcher.java
│ └── test
│ └── java
│ └── org
│ └── logcapture
│ ├── LogCaptureShould.java
│ ├── assertion
│ ├── ExpectedLoggedExceptionShould.java
│ ├── ExpectedLoggingMessageShould.java
│ └── VerificationExceptionShould.java
│ └── matcher
│ ├── ExpectedExceptionMatcherShould.java
│ └── exception
│ ├── ExceptionCauseMatcherShould.java
│ └── ExceptionCauseMessageMatcherShould.java
├── logcapture-example
├── build.gradle
└── src
│ └── test
│ └── java
│ └── org
│ └── logcapture
│ └── example
│ └── ExampleShould.java
├── logcapture-junit4
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── org
│ │ └── logcapture
│ │ └── junit4
│ │ └── LogCaptureRule.java
│ └── test
│ └── java
│ └── org
│ └── logcapture
│ └── junit4
│ ├── LogCaptureClassRuleShould.java
│ └── LogCaptureRuleShould.java
├── logcapture-junit5
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── org
│ │ └── logcapture
│ │ └── junit5
│ │ └── LogCaptureExtension.java
│ └── test
│ └── java
│ └── org
│ └── logcapture
│ └── junit5
│ └── LogCaptureRegisterExtensionShould.java
├── logcapture-kotest
├── build.gradle
└── src
│ ├── main
│ └── kotlin
│ │ └── org
│ │ └── logcapture
│ │ └── kotest
│ │ └── LogCaptureListener.kt
│ └── test
│ └── kotlin
│ └── org
│ └── logcapture
│ └── kotest
│ ├── LogCaptureListenerSpec.kt
│ └── LogCaptureListenerWithCustomLoggerNameSpec.kt
├── logcapture-spock2
├── build.gradle
└── src
│ ├── main
│ └── groovy
│ │ └── org
│ │ └── logcapture
│ │ └── spock2
│ │ ├── LogCaptureSpec.groovy
│ │ └── LogCaptureTrait.groovy
│ └── test
│ └── groovy
│ └── org
│ └── logcapture
│ └── spock2
│ ├── LogCaptureSpecShould.groovy
│ └── LogCaptureTraitShould.groovy
└── settings.gradle
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | end_of_line = lf
9 | charset = utf-8
10 | insert_final_newline = true
11 | indent_style = space
12 |
13 | [*.{java,groovy,kt,gradle}]
14 | indent_size = 2
15 | continuation_indent_size = 4
16 | wildcard_import_limit = 9999
17 | curly_bracket_next_line = false
18 |
19 | [Makefile]
20 | indent_style = tab
21 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # Please see the documentation for all configuration options:
2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
3 |
4 | version: 2
5 | updates:
6 | - package-ecosystem: "gradle"
7 | directory: "/"
8 | schedule:
9 | interval: daily
10 | open-pull-requests-limit: 8
11 | - package-ecosystem: "github-actions"
12 | directory: "/"
13 | schedule:
14 | interval: daily
15 | time: "09:00"
16 | timezone: Europe/Madrid
17 | open-pull-requests-limit: 8
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI Pipeline
2 | on: [push, pull_request]
3 | jobs:
4 | build:
5 | runs-on: ubuntu-latest
6 | strategy:
7 | fail-fast: false
8 | matrix:
9 | java: [11, 21]
10 | name: Java ${{ matrix.java }}
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v4
14 | - name: Set up JDK ${{ matrix.java }}
15 | uses: actions/setup-java@v4
16 | with:
17 | java-version: ${{ matrix.java }}
18 | distribution: adopt-hotspot
19 | - name: Setup Gradle
20 | uses: gradle/actions/setup-gradle@v4
21 | with:
22 | dependency-graph: generate-and-submit
23 | add-job-summary-as-pr-comment: always
24 | - name: Build with Gradle
25 | run: ./gradlew check jacocoTestReport --scan
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | *.iml
3 |
4 | .gradle
5 |
6 | build
7 |
8 | .DS_Store
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017-2020 Javier Salinas
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, 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,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/jsalinaspolo/logcapture)
2 | [](https://repo1.maven.org/maven2/org/logcapture/)
3 | [](https://codecov.io/gh/jsalinaspolo/logcapture)
4 | [](https://snyk.io/test/github/jsalinaspolo/logcapture?targetFile=build.gradle)
5 |
6 | # LogCapture
7 |
8 | LogCapture is a testing library for asserting logging messages.
9 |
10 | > :warning: Latest release with Java 1.8 and/or spock 1.0 support is `1.3.4`
11 | ## How it works
12 |
13 |
14 | Using JUnit Rule:
15 |
16 | ```java
17 | @Rule
18 | public LogCaptureRule logCaptureRule = new LogCaptureRule();
19 |
20 | @Test
21 | public void verify_logs_using_rule() {
22 | log.info("a message");
23 |
24 | logCaptureRule.logged(aLog().info().withMessage("a message"));
25 | }
26 | ```
27 |
28 | Using JUnit 5 Extension:
29 |
30 | ```java
31 | @RegisterExtension
32 | public LogCaptureExtension logCaptureExtension = new LogCaptureExtension();
33 |
34 | @Test
35 | public void verify_logs_using_extension() {
36 | log.info("a message");
37 |
38 | logCaptureExtension.logged(aLog().info().withMessage("a message"));
39 | }
40 | ```
41 |
42 | Using Spock:
43 |
44 | ```groovy
45 | class LogCaptureSpecShould extends LogCaptureSpec {
46 |
47 | @Shared log = LoggerFactory.getLogger(getClass())
48 |
49 | def "verify log message"() {
50 | expect:
51 | log.info("a message");
52 |
53 | logged(aLog().info().withMessage("a message"))
54 | }
55 | }
56 | ```
57 |
58 | Using Kotest:
59 |
60 | ```kotlin
61 | class LogCaptureListenerSpec : StringSpec({
62 | val logCaptureListener = LogCaptureListener()
63 | listener(logCaptureListener) // Add LogCaptureListener
64 |
65 | val log: Logger = LoggerFactory.getLogger(LogCaptureListenerSpec::class.java)
66 |
67 | "verify log messages" {
68 | log.info("a message")
69 |
70 | logCaptureListener.logged(aLog().info().withMessage("a message"))
71 | }
72 | })
73 | ```
74 |
75 | More example how to use the library at [ExampleShould.java](https://github.com/jsalinaspolo/logcapture/blob/main/logcapture-example/src/test/java/org/logcapture/example/ExampleShould.java)
76 |
77 | ## Binaries
78 |
79 | Binaries and dependency information for Maven, Ivy, Gradle and others can be found at [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.logcapture).
80 |
81 | Gradle
82 |
83 | ```
84 | testImplementation 'org.logcapture:logcapture-core:x.y.z'
85 | ```
86 |
87 | add one of the test library dependency
88 |
89 | ```
90 | testImplementation 'org.logcapture:logcapture-junit4:x.y.z'
91 | testImplementation 'org.logcapture:logcapture-junit5:x.y.z'
92 | testImplementation 'org.logcapture:logcapture-spock:x.y.z'
93 | testImplementation 'org.logcapture:logcapture-kotest:x.y.z'
94 | ```
95 |
96 |
97 | Maven:
98 |
99 | ```xml
100 |
101 | org.logcapture
102 | logcapture-core
103 | x.y.z
104 |
105 | ```
106 |
107 |
108 | ## Why LogCapture?
109 |
110 | Logging should be a **first class citizen** in every system that aims to be easily diagnosed and maintained. Logging/testing first could help
111 | you to drive production code. At the same time it is easy to log object references and objects that includes private information like passwords or tokens
112 | and not realising until we actually read production logs.
113 |
114 | We should test how robust are our non-functional capabilities, and not only our functional features. Being able to diagnose,
115 | and ultimately fix, issues is a non-functional dimension that should be subject to the same standards as performance, reliability or security.
116 |
117 | Logging first development could give you the following benefits:
118 |
119 | * Help you to come up with some useful logging that makes sense in context, that exposes enough, and just enough, semantic
120 | information and that does not leak secure information.
121 | * Help you to understand beforehand what are the high level technical details that your design will implement.
122 | * Provide insights to security, support or operations engineers that could have different needs and drivers that application developers.
123 | * Help you to come up with rules for your logging monitoring system.
124 |
125 | ## License
126 |
127 | This project is licensed under [MIT license](http://opensource.org/licenses/MIT).
128 |
129 | ## Contributing
130 |
131 | Github is for social coding: if you want to write code, I encourage contributions through pull requests from forks of this repository.
132 | Create Github tickets for bugs and new features and comment on the ones that you are interested in.
133 |
--------------------------------------------------------------------------------
/RELEASE.md:
--------------------------------------------------------------------------------
1 | ## How to release
2 |
3 | Use username/password of sonatype https://s01.oss.sonatype.org/
4 |
5 | ```shell
6 | MAVEN_USERNAME={} MAVEN_PASSWORD={} ./gradlew build jar publish
7 | ```
8 |
9 | ## Generate gpg key
10 |
11 | Generate gpg key with name and email
12 |
13 | ```shell
14 | gpg --gen-key
15 | ```
16 |
17 | Export keyring
18 |
19 | ```shell
20 | gpg --keyring secring.gpg --export-secret-keys > ~/.gnupg/secring.gpg
21 | ```
22 |
23 | ## Gradle properties
24 |
25 | Gradle properties should looks something like
26 |
27 | ```shell
28 | $ cat ~/.gradle/gradle.properties
29 |
30 | signing.keyId=5DBFACD8
31 | signing.password={passphrase}
32 | signing.secretKeyRingFile=//Users/jsalinas/.gnupg/secring.gpg
33 | ```
34 |
35 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | group 'org.logcapture'
2 | version '1.3.6'
3 | description = 'A testing library for assert logging messages.'
4 |
5 | repositories {
6 | mavenCentral()
7 | }
8 |
9 | subprojects {
10 | apply plugin: 'idea'
11 | apply plugin: 'signing'
12 | apply plugin: 'maven-publish'
13 | apply plugin: 'java-library'
14 | apply plugin: "jacoco"
15 |
16 | repositories {
17 | mavenCentral()
18 | }
19 |
20 | test {
21 | testLogging {
22 | events "passed", "skipped", "failed"
23 | }
24 | }
25 |
26 | java {
27 | toolchain {
28 | languageVersion = JavaLanguageVersion.of(11)
29 | }
30 |
31 | withJavadocJar()
32 | withSourcesJar()
33 | }
34 |
35 | def pomConfig = {
36 | name project.name
37 | description 'A testing library for assert logging messages.'
38 | url "http://www.jspcore.com/"
39 |
40 | licenses {
41 | license {
42 | name "MIT License"
43 | url "https://opensource.org/licenses/MIT"
44 | distribution "repo"
45 | }
46 | }
47 | scm {
48 | connection "scm:https://jsalinaspolo@github.com/jsalinaspolo/logcapture.git"
49 | developerConnection "scm:git@github.com:jsalinaspolo/logcapture.git"
50 | url "https://github.com/jsalinaspolo/logcapture"
51 | }
52 | developers {
53 | developer {
54 | id "jsalinaspolo"
55 | name "Team JSPCore"
56 | }
57 | }
58 | }
59 |
60 | publishing {
61 | repositories {
62 | maven {
63 | name = "OSSRH"
64 | url = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
65 | credentials {
66 | username = System.getenv("MAVEN_USERNAME")
67 | password = System.getenv("MAVEN_PASSWORD")
68 | }
69 | }
70 | }
71 | publications {
72 | MyPublication(MavenPublication) {
73 | from components.java
74 | groupId 'org.logcapture'
75 | artifactId project.name
76 | version "1.3.6"
77 |
78 | pom.withXml {
79 | def root = asNode()
80 | root.appendNode('description', 'A testing library for assert logging messages.')
81 | root.children().last() + pomConfig
82 | }
83 | }
84 | }
85 | }
86 |
87 | signing {
88 | sign publishing.publications.MyPublication
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.parallel=true
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jsalinaspolo/logcapture/e7b1d737644c057efb2ee0280c42733e481329f5/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
88 |
89 | # Use the maximum available, or set MAX_FD != -1 to use that value.
90 | MAX_FD=maximum
91 |
92 | warn () {
93 | echo "$*"
94 | } >&2
95 |
96 | die () {
97 | echo
98 | echo "$*"
99 | echo
100 | exit 1
101 | } >&2
102 |
103 | # OS specific support (must be 'true' or 'false').
104 | cygwin=false
105 | msys=false
106 | darwin=false
107 | nonstop=false
108 | case "$( uname )" in #(
109 | CYGWIN* ) cygwin=true ;; #(
110 | Darwin* ) darwin=true ;; #(
111 | MSYS* | MINGW* ) msys=true ;; #(
112 | NONSTOP* ) nonstop=true ;;
113 | esac
114 |
115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
116 |
117 |
118 | # Determine the Java command to use to start the JVM.
119 | if [ -n "$JAVA_HOME" ] ; then
120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
121 | # IBM's JDK on AIX uses strange locations for the executables
122 | JAVACMD=$JAVA_HOME/jre/sh/java
123 | else
124 | JAVACMD=$JAVA_HOME/bin/java
125 | fi
126 | if [ ! -x "$JAVACMD" ] ; then
127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
128 |
129 | Please set the JAVA_HOME variable in your environment to match the
130 | location of your Java installation."
131 | fi
132 | else
133 | JAVACMD=java
134 | if ! command -v java >/dev/null 2>&1
135 | then
136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 | fi
142 |
143 | # Increase the maximum file descriptors if we can.
144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
145 | case $MAX_FD in #(
146 | max*)
147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
148 | # shellcheck disable=SC2039,SC3045
149 | MAX_FD=$( ulimit -H -n ) ||
150 | warn "Could not query maximum file descriptor limit"
151 | esac
152 | case $MAX_FD in #(
153 | '' | soft) :;; #(
154 | *)
155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
156 | # shellcheck disable=SC2039,SC3045
157 | ulimit -n "$MAX_FD" ||
158 | warn "Could not set maximum file descriptor limit to $MAX_FD"
159 | esac
160 | fi
161 |
162 | # Collect all arguments for the java command, stacking in reverse order:
163 | # * args from the command line
164 | # * the main class name
165 | # * -classpath
166 | # * -D...appname settings
167 | # * --module-path (only if needed)
168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
169 |
170 | # For Cygwin or MSYS, switch paths to Windows format before running java
171 | if "$cygwin" || "$msys" ; then
172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
174 |
175 | JAVACMD=$( cygpath --unix "$JAVACMD" )
176 |
177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
178 | for arg do
179 | if
180 | case $arg in #(
181 | -*) false ;; # don't mess with options #(
182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
183 | [ -e "$t" ] ;; #(
184 | *) false ;;
185 | esac
186 | then
187 | arg=$( cygpath --path --ignore --mixed "$arg" )
188 | fi
189 | # Roll the args list around exactly as many times as the number of
190 | # args, so each arg winds up back in the position where it started, but
191 | # possibly modified.
192 | #
193 | # NB: a `for` loop captures its iteration list before it begins, so
194 | # changing the positional parameters here affects neither the number of
195 | # iterations, nor the values presented in `arg`.
196 | shift # remove old arg
197 | set -- "$@" "$arg" # push replacement arg
198 | done
199 | fi
200 |
201 |
202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
204 |
205 | # Collect all arguments for the java command:
206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
207 | # and any embedded shellness will be escaped.
208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
209 | # treated as '${Hostname}' itself on the command line.
210 |
211 | set -- \
212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
213 | -classpath "$CLASSPATH" \
214 | org.gradle.wrapper.GradleWrapperMain \
215 | "$@"
216 |
217 | # Stop when "xargs" is not available.
218 | if ! command -v xargs >/dev/null 2>&1
219 | then
220 | die "xargs is not available"
221 | fi
222 |
223 | # Use "xargs" to parse quoted args.
224 | #
225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
226 | #
227 | # In Bash we could simply go:
228 | #
229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
230 | # set -- "${ARGS[@]}" "$@"
231 | #
232 | # but POSIX shell has neither arrays nor command substitution, so instead we
233 | # post-process each arg (as a line of input to sed) to backslash-escape any
234 | # character that might be a shell metacharacter, then use eval to reverse
235 | # that process (while maintaining the separation between arguments), and wrap
236 | # the whole thing up as a single "set" statement.
237 | #
238 | # This will of course break if any of these variables contains a newline or
239 | # an unmatched quote.
240 | #
241 |
242 | eval "set -- $(
243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
244 | xargs -n1 |
245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
246 | tr '\n' ' '
247 | )" '"$@"'
248 |
249 | exec "$JAVACMD" "$@"
250 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo. 1>&2
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
48 | echo. 1>&2
49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
50 | echo location of your Java installation. 1>&2
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo. 1>&2
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
62 | echo. 1>&2
63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
64 | echo location of your Java installation. 1>&2
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/logcapture-core/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java'
3 | }
4 |
5 | dependencies {
6 | api "org.hamcrest:hamcrest-library:3.0"
7 | api "ch.qos.logback:logback-classic:1.5.18"
8 | api "org.slf4j:slf4j-api:2.0.17"
9 |
10 | testImplementation "junit:junit:4.13.2"
11 | testImplementation "org.assertj:assertj-core:3.27.3"
12 | }
13 |
--------------------------------------------------------------------------------
/logcapture-core/src/main/java/org/logcapture/LogCapture.java:
--------------------------------------------------------------------------------
1 | package org.logcapture;
2 |
3 | import ch.qos.logback.classic.spi.ILoggingEvent;
4 | import org.hamcrest.Matcher;
5 | import org.logcapture.assertion.VerificationException;
6 |
7 | import java.util.Collections;
8 | import java.util.List;
9 | import java.util.stream.Collectors;
10 |
11 | public class LogCapture {
12 |
13 | private final List events;
14 |
15 | public LogCapture(List events) {
16 | this.events = events;
17 | }
18 |
19 | public LogCapture logged(Matcher> expectedLoggingMessage) {
20 | if (expectedLoggingMessage.matches(events)) {
21 | return this;
22 | }
23 |
24 | throw VerificationException.forUnmatchedLog(expectedLoggingMessage, events);
25 | }
26 |
27 | public LogCapture logged(Matcher> expectedLoggingMessage, Integer times) {
28 | logged(expectedLoggingMessage);
29 | List matchingEvents = events.stream()
30 | .filter(actual -> expectedLoggingMessage.matches(Collections.singletonList(actual)))
31 | .collect(Collectors.toList());
32 | if (matchingEvents.size() != times) {
33 | throw VerificationException.forUnmatchedTimesLog(expectedLoggingMessage, events, times, events.size());
34 | }
35 | return this;
36 | }
37 |
38 | public List getEvents() {
39 | return Collections.unmodifiableList(events);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/logcapture-core/src/main/java/org/logcapture/assertion/ExpectedLoggedException.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.assertion;
2 |
3 | import ch.qos.logback.classic.spi.ILoggingEvent;
4 | import org.logcapture.matcher.ExpectedExceptionMatcher;
5 | import org.logcapture.matcher.TypedAnythingMatcher;
6 | import org.hamcrest.Matcher;
7 |
8 | public class ExpectedLoggedException {
9 | private Matcher expectedMessageMatcher = new TypedAnythingMatcher<>();
10 | private Matcher expectedException = new TypedAnythingMatcher<>();
11 |
12 | private ExpectedLoggedException() {
13 | }
14 |
15 | public static ExpectedLoggedException logException() {
16 | return new ExpectedLoggedException();
17 | }
18 |
19 | public ExpectedLoggedException withException(Matcher extends Exception> exceptionMatcher) {
20 | this.expectedException = new ExpectedExceptionMatcher(exceptionMatcher);
21 | return this;
22 | }
23 |
24 | public boolean matches(ILoggingEvent event) {
25 | if (event.getThrowableProxy() == null) {
26 | return false;
27 | }
28 |
29 | return expectedMessageMatcher.matches(event.getThrowableProxy().getMessage()) &&
30 | expectedException.matches(event.getThrowableProxy());
31 | }
32 |
33 | public ExpectedLoggedException withMessage(Matcher expectedMessageMatcher) {
34 | this.expectedMessageMatcher = expectedMessageMatcher;
35 | return this;
36 | }
37 |
38 | @Override
39 | public String toString() {
40 | return "ExpectedLoggedException{" +
41 | "expectedMessageMatcher=" + expectedMessageMatcher +
42 | ", expectedException=" + expectedException +
43 | '}';
44 | }
45 |
46 | public static final ExpectedLoggedException ANYTHING = new ExpectedLoggedException() {
47 | public boolean matches(ILoggingEvent event) {
48 | return true;
49 | }
50 | };
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/logcapture-core/src/main/java/org/logcapture/assertion/ExpectedLoggingMessage.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.assertion;
2 |
3 | import ch.qos.logback.classic.Level;
4 | import ch.qos.logback.classic.spi.ILoggingEvent;
5 | import org.logcapture.matcher.TypedAnythingMatcher;
6 | import org.hamcrest.Description;
7 | import org.hamcrest.Matcher;
8 | import org.hamcrest.TypeSafeMatcher;
9 | import org.slf4j.Marker;
10 | import org.slf4j.MarkerFactory;
11 |
12 | import java.util.ArrayList;
13 | import java.util.Arrays;
14 | import java.util.Collection;
15 | import java.util.HashMap;
16 | import java.util.List;
17 | import java.util.Map;
18 | import java.util.function.Predicate;
19 | import java.util.stream.Collectors;
20 |
21 | import static ch.qos.logback.classic.Level.DEBUG;
22 | import static ch.qos.logback.classic.Level.ERROR;
23 | import static ch.qos.logback.classic.Level.INFO;
24 | import static ch.qos.logback.classic.Level.WARN;
25 | import static java.util.Collections.singleton;
26 | import static org.hamcrest.Matchers.equalTo;
27 |
28 | public class ExpectedLoggingMessage extends TypeSafeMatcher> {
29 |
30 | private Matcher logLevelMatcher = new TypedAnythingMatcher<>();
31 | private Matcher markerMatcher = new TypedAnythingMatcher<>();
32 | private List> expectedMessageMatcher = new ArrayList<>();
33 | private Matcher expectedLengthMatcher = new TypedAnythingMatcher<>();
34 | private Matcher expectedLoggerNameMatcher = new TypedAnythingMatcher<>();
35 | private ExpectedLoggedException expectedLoggedException = ExpectedLoggedException.ANYTHING;
36 | private Map> mdcMatcher = new HashMap<>();
37 |
38 | private ExpectedLoggingMessage() {
39 | }
40 |
41 | public static ExpectedLoggingMessage aLog() {
42 | return new ExpectedLoggingMessage();
43 | }
44 |
45 | public ExpectedLoggingMessage withLevel(Matcher errorLevel) {
46 | logLevelMatcher = errorLevel;
47 | return this;
48 | }
49 |
50 | public ExpectedLoggingMessage withLevel(Level errorLevel) {
51 | return withLevel(equalTo(errorLevel));
52 | }
53 |
54 | public ExpectedLoggingMessage withMarker(Matcher marker) {
55 | markerMatcher = marker;
56 | return this;
57 | }
58 |
59 | @Override
60 | protected boolean matchesSafely(List events) {
61 | return events.stream().anyMatch(this::matches);
62 | }
63 |
64 | private boolean matches(ILoggingEvent event) {
65 | return logLevelMatcher.matches(event.getLevel()) &&
66 | markerMatcher.matches(event.getMarker()) &&
67 | expectedMessageMatcher.stream().allMatch(matcher -> matcher.matches(event.getFormattedMessage())) &&
68 | expectedLoggedException.matches(event) &&
69 | expectedLoggerNameMatcher.matches(event.getLoggerName()) &&
70 | expectedLengthMatcher.matches(event.getFormattedMessage().length()) &&
71 | matchesMdc(event.getMDCPropertyMap());
72 | }
73 |
74 | public ExpectedLoggingMessage withMarker(Marker marker) {
75 | return withMarker(equalTo(marker));
76 | }
77 |
78 | public ExpectedLoggingMessage withMarker(String marker) {
79 | return withMarker(MarkerFactory.getMarker(marker));
80 | }
81 |
82 | public ExpectedLoggingMessage debug() {
83 | logLevelMatcher = equalTo(DEBUG);
84 | return this;
85 | }
86 |
87 | public ExpectedLoggingMessage info() {
88 | logLevelMatcher = equalTo(INFO);
89 | return this;
90 | }
91 |
92 | public ExpectedLoggingMessage warn() {
93 | logLevelMatcher = equalTo(WARN);
94 | return this;
95 | }
96 |
97 | public ExpectedLoggingMessage error() {
98 | logLevelMatcher = equalTo(ERROR);
99 | return this;
100 | }
101 |
102 | private boolean matchesMdc(Map mdcPropertyMap) {
103 | return mdcMatcher.entrySet().stream()
104 | .allMatch(entry -> entry.getValue().matches(mdcPropertyMap.get(entry.getKey())));
105 | }
106 |
107 | public final ExpectedLoggingMessage withMessage(Matcher expectedMessages) {
108 | return withMessage(singleton(expectedMessages));
109 | }
110 |
111 | @SafeVarargs
112 | public final ExpectedLoggingMessage withMessage(Matcher... expectedMessages) {
113 | return withMessage(Arrays.asList(expectedMessages));
114 | }
115 |
116 | public final ExpectedLoggingMessage withMessage(Collection> expectedMessages) {
117 | expectedMessageMatcher.addAll(expectedMessages);
118 | return this;
119 | }
120 |
121 | public ExpectedLoggingMessage withMessage(String expectedMessage) {
122 | return withMessage(equalTo(expectedMessage));
123 | }
124 |
125 | public ExpectedLoggingMessage withLoggerName(Matcher expectedLoggerName) {
126 | expectedLoggerNameMatcher = expectedLoggerName;
127 | return this;
128 | }
129 |
130 | public ExpectedLoggingMessage havingException(ExpectedLoggedException expectedLoggedException) {
131 | this.expectedLoggedException = expectedLoggedException;
132 | return this;
133 | }
134 |
135 | public ExpectedLoggingMessage length(Matcher expectedLengthMatcher) {
136 | this.expectedLengthMatcher = expectedLengthMatcher;
137 | return this;
138 | }
139 |
140 | public ExpectedLoggingMessage withMdc(String mdcKey, String mdcValue) {
141 | withMdc(mdcKey, equalTo(mdcValue));
142 | return this;
143 | }
144 |
145 | public ExpectedLoggingMessage withMdc(String mdcKey, Matcher mdcValue) {
146 | mdcMatcher.put(mdcKey, mdcValue);
147 | return this;
148 | }
149 |
150 | @Override
151 | public String toString() {
152 | return "ExpectedLoggingMessage{" + description() + '}';
153 | }
154 |
155 | private String description() {
156 | List results = new ArrayList<>();
157 |
158 | results.addAll(toList("logLevelMatcher", logLevelMatcher));
159 | results.addAll(toList("markerMatcher", markerMatcher));
160 | results.addAll(toList("expectedMessageMatcher", expectedMessageMatcher));
161 | results.addAll(toList("expectedLengthMatcher", expectedLengthMatcher));
162 | results.addAll(toList("expectedLoggerNameMatcher", expectedLoggerNameMatcher));
163 | results.addAll(toList("expectedMdc", mdcMatcher));
164 |
165 | if (!(expectedLoggedException == ExpectedLoggedException.ANYTHING)) {
166 | results.add("expectedLoggedException" + "=" + expectedLoggedException);
167 | }
168 |
169 | return results.stream().collect(Collectors.joining(", "));
170 | }
171 |
172 | private List toList(String fieldName, Object field) {
173 | List description = new ArrayList<>();
174 | if (!(field instanceof TypedAnythingMatcher)) {
175 | description.add(fieldName + "=" + field);
176 | }
177 | return description;
178 | }
179 |
180 | @Override
181 | public void describeTo(Description description) {
182 | description.appendText(description());
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/logcapture-core/src/main/java/org/logcapture/assertion/VerificationException.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.assertion;
2 |
3 | import ch.qos.logback.classic.spi.ILoggingEvent;
4 | import org.hamcrest.Matcher;
5 |
6 | import java.util.List;
7 | import java.util.stream.Collectors;
8 |
9 | public class VerificationException extends AssertionError {
10 |
11 | private VerificationException(String message) {
12 | super(message);
13 | }
14 |
15 | public static VerificationException forUnmatchedLog(Matcher> expectedLogMessage, List logEvents) {
16 | return new VerificationException(String.format(
17 | "Expected matching: \n%s\nLogs received: \n%s",
18 | expectedLogMessage.toString(),
19 | logEvents.stream()
20 | .map(VerificationException::formatLogEvent)
21 | .collect(Collectors.joining("\n"))
22 | ));
23 | }
24 |
25 | public static VerificationException forUnmatchedTimesLog(Matcher> expectedLogMessage, List logEvents, Integer expectedTimes, Integer times) {
26 | return new VerificationException(String.format(
27 | "Expected %d times but got %d: \n%s\nLogs matched: \n%s",
28 | expectedTimes,
29 | times,
30 | expectedLogMessage.toString(),
31 | logEvents.stream()
32 | .map(VerificationException::formatLogEvent)
33 | .collect(Collectors.joining("\n"))
34 | ));
35 | }
36 |
37 | private static String formatLogEvent(ILoggingEvent log) {
38 | return String.format("level: %s marker: %s mdc: %s message: %s", log.getLevel(),
39 | log.getMarker(),
40 | log.getMDCPropertyMap(),
41 | log.getFormattedMessage());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/logcapture-core/src/main/java/org/logcapture/logback/StubAppender.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.logback;
2 |
3 | import ch.qos.logback.classic.spi.ILoggingEvent;
4 | import ch.qos.logback.core.Appender;
5 | import ch.qos.logback.core.Context;
6 | import ch.qos.logback.core.LogbackException;
7 | import ch.qos.logback.core.filter.Filter;
8 | import ch.qos.logback.core.spi.FilterReply;
9 | import ch.qos.logback.core.status.Status;
10 |
11 | import java.util.List;
12 | import java.util.concurrent.CopyOnWriteArrayList;
13 |
14 | public class StubAppender implements Appender {
15 | public static final String STUB_APPENDER_NAME = "stub-appender";
16 | private final List loggedEvents = new CopyOnWriteArrayList<>();
17 |
18 | @Override
19 | public String getName() {
20 | return STUB_APPENDER_NAME;
21 | }
22 |
23 | @Override
24 | public void doAppend(ILoggingEvent iLoggingEvent) throws LogbackException {
25 | loggedEvents.add(iLoggingEvent);
26 | }
27 |
28 | @Override
29 | public void setName(String s) {
30 | }
31 |
32 | @Override
33 | public void setContext(Context context) {
34 | }
35 |
36 | @Override
37 | public Context getContext() {
38 | return null;
39 | }
40 |
41 | @Override
42 | public void addStatus(Status status) {
43 | }
44 |
45 | @Override
46 | public void addInfo(String s) {
47 | }
48 |
49 | @Override
50 | public void addInfo(String s, Throwable throwable) {
51 | }
52 |
53 | @Override
54 | public void addWarn(String s) {
55 | }
56 |
57 | @Override
58 | public void addWarn(String s, Throwable throwable) {
59 | }
60 |
61 | @Override
62 | public void addError(String s) {
63 | }
64 |
65 | @Override
66 | public void addError(String s, Throwable throwable) {
67 | }
68 |
69 | @Override
70 | public void addFilter(Filter filter) {
71 |
72 | }
73 |
74 | @Override
75 | public void clearAllFilters() {
76 | loggedEvents.clear();
77 | }
78 |
79 | @Override
80 | public List> getCopyOfAttachedFiltersList() {
81 | return null;
82 | }
83 |
84 | @Override
85 | public FilterReply getFilterChainDecision(ILoggingEvent iLoggingEvent) {
86 | return null;
87 | }
88 |
89 | @Override
90 | public void start() {
91 | }
92 |
93 | @Override
94 | public void stop() {
95 | }
96 |
97 | @Override
98 | public boolean isStarted() {
99 | return true;
100 | }
101 |
102 | public List events() {
103 | return loggedEvents;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/logcapture-core/src/main/java/org/logcapture/matcher/ExpectedExceptionMatcher.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.matcher;
2 |
3 | import ch.qos.logback.classic.spi.ThrowableProxy;
4 | import org.hamcrest.BaseMatcher;
5 | import org.hamcrest.Description;
6 | import org.hamcrest.Matcher;
7 |
8 | public class ExpectedExceptionMatcher extends BaseMatcher {
9 | private final Matcher extends Exception> expectedException;
10 |
11 | public ExpectedExceptionMatcher(Matcher extends Exception> expectedException) {
12 | this.expectedException = expectedException;
13 | }
14 |
15 | @Override
16 | public boolean matches(Object item) {
17 | if (item instanceof ThrowableProxy) {
18 | return expectedException.matches(((ThrowableProxy) item).getThrowable());
19 | }
20 |
21 | return false;
22 | }
23 |
24 | @Override
25 | public void describeTo(Description description) {
26 | expectedException.describeTo(description);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/logcapture-core/src/main/java/org/logcapture/matcher/TypedAnythingMatcher.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.matcher;
2 |
3 | import org.hamcrest.BaseMatcher;
4 | import org.hamcrest.Description;
5 |
6 | public class TypedAnythingMatcher extends BaseMatcher {
7 |
8 | @Override
9 | public boolean matches(Object item) {
10 | return true;
11 | }
12 |
13 | @Override
14 | public void describeTo(Description description) {
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/logcapture-core/src/main/java/org/logcapture/matcher/exception/ExceptionCauseMatcher.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.matcher.exception;
2 |
3 | import org.hamcrest.BaseMatcher;
4 | import org.hamcrest.Description;
5 | import org.hamcrest.Matcher;
6 |
7 | public class ExceptionCauseMatcher extends BaseMatcher {
8 |
9 | private final Class extends Exception> exceptionClass;
10 |
11 | private ExceptionCauseMatcher(Class extends Exception> exceptionClass) {
12 | this.exceptionClass = exceptionClass;
13 | }
14 |
15 | public static Matcher causeOf(Class extends Exception> exceptionClass) {
16 | return new ExceptionCauseMatcher(exceptionClass);
17 | }
18 |
19 | @Override
20 | public boolean matches(Object exception) {
21 | Throwable expectedExceptionCause = ((Throwable) exception).getCause();
22 | return exceptionClass.isInstance(expectedExceptionCause);
23 | }
24 |
25 | @Override
26 | public void describeTo(Description description) {
27 | description.appendText("Expecting exception to be instance of " + exceptionClass);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/logcapture-core/src/main/java/org/logcapture/matcher/exception/ExceptionCauseMessageMatcher.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.matcher.exception;
2 |
3 | import org.hamcrest.BaseMatcher;
4 | import org.hamcrest.Description;
5 | import org.hamcrest.Matcher;
6 |
7 | public class ExceptionCauseMessageMatcher extends BaseMatcher {
8 |
9 | private final Matcher causeMatcher;
10 |
11 | private ExceptionCauseMessageMatcher(Matcher causeMatcher) {
12 | this.causeMatcher = causeMatcher;
13 | }
14 |
15 | public static Matcher whereCauseMessage(Matcher causeMatcher) {
16 | return new ExceptionCauseMessageMatcher(causeMatcher);
17 | }
18 |
19 | @Override
20 | public boolean matches(Object exception) {
21 | final Throwable expectedExceptionCause = ((Throwable) exception).getCause();
22 | return causeMatcher.matches(expectedExceptionCause.getMessage());
23 | }
24 |
25 | @Override
26 | public void describeTo(Description description) {
27 | description.appendText("Expecting exception cause to contain " + causeMatcher);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/logcapture-core/src/test/java/org/logcapture/LogCaptureShould.java:
--------------------------------------------------------------------------------
1 | package org.logcapture;
2 |
3 | import ch.qos.logback.classic.Level;
4 | import ch.qos.logback.classic.Logger;
5 | import ch.qos.logback.classic.spi.ILoggingEvent;
6 | import ch.qos.logback.classic.spi.LoggingEvent;
7 | import org.assertj.core.api.Assertions;
8 | import org.junit.Test;
9 | import org.logcapture.assertion.ExpectedLoggingMessage;
10 | import org.logcapture.assertion.VerificationException;
11 | import org.slf4j.LoggerFactory;
12 |
13 | import java.util.Arrays;
14 |
15 | import static ch.qos.logback.classic.Level.INFO;
16 | import static org.logcapture.assertion.ExpectedLoggingMessage.aLog;
17 |
18 | public class LogCaptureShould {
19 |
20 | @Test
21 | public void match_n_times() {
22 | LoggingEvent log1 = aLoggingEventWith(INFO, "message");
23 |
24 | LogCapture underTest = new LogCapture<>(Arrays.asList(log1, log1));
25 |
26 | underTest.logged(aLog().withMessage("message"), 2);
27 | }
28 |
29 | @Test
30 | public void match_n_times_filtering_others() {
31 | LoggingEvent log1 = aLoggingEventWith(INFO, "message");
32 | LoggingEvent log2 = aLoggingEventWith(INFO, "another");
33 |
34 | LogCapture underTest = new LogCapture<>(Arrays.asList(log1, log1, log2));
35 |
36 | ExpectedLoggingMessage expectedLog = aLog().withMessage("message");
37 | underTest.logged(expectedLog, 2);
38 | }
39 |
40 | @Test
41 | public void match_n_times_multiples_messages() {
42 | LoggingEvent log1 = aLoggingEventWith(INFO, "message");
43 | LoggingEvent log2 = aLoggingEventWith(INFO, "another-message");
44 |
45 | LogCapture underTest = new LogCapture<>(Arrays.asList(log1, log2, log1, log2));
46 |
47 | underTest.logged(aLog().withMessage("message"), 2);
48 | }
49 |
50 | @Test
51 | public void fail_matching_n_times() {
52 | LoggingEvent log1 = aLoggingEventWith(INFO, "message");
53 |
54 | LogCapture underTest = new LogCapture<>(Arrays.asList(log1, log1));
55 |
56 | Assertions.assertThatThrownBy(
57 | () -> underTest.logged(aLog().withMessage("message"), 1)
58 | ).isInstanceOf(VerificationException.class)
59 | .hasMessageContaining("Expected 1 times but got 2:");
60 |
61 | }
62 |
63 | private LoggingEvent aLoggingEventWith(Level level, String message) {
64 | return aLoggingEventWith(level, message, null);
65 | }
66 |
67 | private LoggingEvent aLoggingEventWith(Level level, String message, Exception exception) {
68 | Logger log = (Logger) LoggerFactory.getLogger(this.getClass());
69 | return new LoggingEvent("fqcn", log, level, message, exception, null);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/logcapture-core/src/test/java/org/logcapture/assertion/ExpectedLoggedExceptionShould.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.assertion;
2 |
3 | import ch.qos.logback.classic.Level;
4 | import ch.qos.logback.classic.Logger;
5 | import ch.qos.logback.classic.spi.LoggingEvent;
6 | import org.junit.Test;
7 | import org.slf4j.LoggerFactory;
8 |
9 | import static ch.qos.logback.classic.Level.INFO;
10 | import static org.assertj.core.api.Assertions.assertThat;
11 | import static org.hamcrest.Matchers.equalTo;
12 | import static org.hamcrest.Matchers.isA;
13 |
14 | public class ExpectedLoggedExceptionShould {
15 |
16 | @Test
17 | public void match_when_exception_class_match() {
18 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message", new IllegalArgumentException());
19 | ExpectedLoggedException expectedLoggedException = ExpectedLoggedException.logException()
20 | .withException(isA(IllegalArgumentException.class));
21 |
22 | boolean matches = expectedLoggedException.matches(logEvent);
23 |
24 | assertThat(matches).isTrue();
25 | }
26 |
27 | @Test
28 | public void match_when_exception_message_match() {
29 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message", new IllegalArgumentException("message error"));
30 | ExpectedLoggedException expectedLoggedException = ExpectedLoggedException.logException()
31 | .withMessage(equalTo("message error"));
32 |
33 | boolean matches = expectedLoggedException.matches(logEvent);
34 |
35 | assertThat(matches).isTrue();
36 | }
37 |
38 | @Test
39 | public void not_match_when_exception_class_different() {
40 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message", new IllegalArgumentException());
41 | ExpectedLoggedException expectedLoggedException = ExpectedLoggedException.logException()
42 | .withException(isA(IllegalStateException.class));
43 |
44 | boolean matches = expectedLoggedException.matches(logEvent);
45 |
46 | assertThat(matches).isFalse();
47 | }
48 |
49 | @Test
50 | public void not_match_when_exception_message_different() {
51 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message", new IllegalArgumentException("some error"));
52 | ExpectedLoggedException expectedLoggedException = ExpectedLoggedException.logException()
53 | .withMessage(equalTo("another error"));
54 |
55 | boolean matches = expectedLoggedException.matches(logEvent);
56 |
57 | assertThat(matches).isFalse();
58 | }
59 |
60 | @Test
61 | public void to_string_method_when_exception_not_match() {
62 | ExpectedLoggedException expectedLoggedException = ExpectedLoggedException.logException()
63 | .withMessage(equalTo("another error"))
64 | .withException(isA(IllegalArgumentException.class));
65 |
66 | assertThat(expectedLoggedException.toString()).isEqualTo("ExpectedLoggedException{expectedMessageMatcher=\"another error\", expectedException=is an instance of java.lang.IllegalArgumentException}");
67 | }
68 |
69 | private LoggingEvent aLoggingEventWith(Level level, String message, Exception exception) {
70 | Logger log = (Logger) LoggerFactory.getLogger(this.getClass());
71 | return new LoggingEvent("fqcn", log, level, message, exception, null);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/logcapture-core/src/test/java/org/logcapture/assertion/ExpectedLoggingMessageShould.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.assertion;
2 |
3 | import ch.qos.logback.classic.Level;
4 | import ch.qos.logback.classic.Logger;
5 | import ch.qos.logback.classic.spi.LoggingEvent;
6 | import org.junit.Test;
7 | import org.slf4j.LoggerFactory;
8 | import org.slf4j.MDC;
9 | import org.slf4j.MarkerFactory;
10 |
11 | import static ch.qos.logback.classic.Level.DEBUG;
12 | import static ch.qos.logback.classic.Level.ERROR;
13 | import static ch.qos.logback.classic.Level.INFO;
14 | import static ch.qos.logback.classic.Level.WARN;
15 | import static org.logcapture.assertion.ExpectedLoggedException.logException;
16 | import static org.logcapture.assertion.ExpectedLoggingMessage.aLog;
17 | import static org.logcapture.matcher.exception.ExceptionCauseMatcher.causeOf;
18 | import static java.util.Arrays.asList;
19 | import static java.util.Collections.emptyList;
20 | import static java.util.Collections.singletonList;
21 | import static org.assertj.core.api.Assertions.assertThat;
22 | import static org.hamcrest.Matchers.containsString;
23 | import static org.hamcrest.Matchers.equalTo;
24 | import static org.hamcrest.Matchers.instanceOf;
25 |
26 | public class ExpectedLoggingMessageShould {
27 |
28 | @Test
29 | public void match_when_at_least_one_element_is_matching() {
30 | LoggingEvent matchingEvent = aLoggingEventWith(INFO, "message");
31 | LoggingEvent notMatchingEvent = aLoggingEventWith(INFO, "message");
32 | ExpectedLoggingMessage expectedLoggingMessage = aLog().withLevel(equalTo(INFO));
33 |
34 | boolean matches = expectedLoggingMessage.matches(asList(matchingEvent, notMatchingEvent));
35 |
36 | assertThat(matches).isTrue();
37 | }
38 |
39 | @Test
40 | public void no_match_when_there_are_no_events() {
41 | ExpectedLoggingMessage expectedLoggingMessage = aLog().withLevel(equalTo(INFO));
42 |
43 | boolean matches = expectedLoggingMessage.matches(emptyList());
44 |
45 | assertThat(matches).isFalse();
46 | }
47 |
48 | @Test
49 | public void not_match_when_no_element_is_matching() {
50 | LoggingEvent notMatchingEvent1 = aLoggingEventWith(INFO, "message");
51 | LoggingEvent notMatchingEvent2 = aLoggingEventWith(INFO, "message");
52 | ExpectedLoggingMessage expectedLoggingMessage = aLog().withLevel(equalTo(DEBUG));
53 |
54 | boolean matches = expectedLoggingMessage.matches(asList(notMatchingEvent1, notMatchingEvent2));
55 |
56 | assertThat(matches).isFalse();
57 | }
58 |
59 | @Test
60 | public void match_when_log_level_match() {
61 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message");
62 | ExpectedLoggingMessage expectedLoggingMessage = aLog().withLevel(equalTo(INFO));
63 |
64 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
65 |
66 | assertThat(matches).isTrue();
67 | }
68 |
69 | @Test
70 | public void match_when_debug_log_level_match() {
71 | LoggingEvent logEvent = aLoggingEventWith(DEBUG, "message");
72 | ExpectedLoggingMessage expectedLoggingMessage = aLog().debug();
73 |
74 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
75 |
76 | assertThat(matches).isTrue();
77 | }
78 |
79 | @Test
80 | public void match_when_info_log_level_match() {
81 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message");
82 | ExpectedLoggingMessage expectedLoggingMessage = aLog().info();
83 |
84 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
85 |
86 | assertThat(matches).isTrue();
87 | }
88 |
89 | @Test
90 | public void match_when_warn_log_level_match() {
91 | LoggingEvent logEvent = aLoggingEventWith(WARN, "message");
92 | ExpectedLoggingMessage expectedLoggingMessage = aLog().warn();
93 |
94 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
95 |
96 | assertThat(matches).isTrue();
97 | }
98 |
99 | @Test
100 | public void match_when_error_log_level_match() {
101 | LoggingEvent logEvent = aLoggingEventWith(ERROR, "message");
102 | ExpectedLoggingMessage expectedLoggingMessage = aLog().error();
103 |
104 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
105 |
106 | assertThat(matches).isTrue();
107 | }
108 |
109 | @Test
110 | public void match_when_message_match() {
111 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message");
112 | ExpectedLoggingMessage expectedLoggingMessage = aLog().withMessage(equalTo("message"));
113 |
114 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
115 |
116 | assertThat(matches).isTrue();
117 | }
118 |
119 | @Test
120 | public void match_when_multiple_messages_matches() {
121 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message has another message");
122 |
123 | ExpectedLoggingMessage expectedLoggingMessage = aLog().withMessage(containsString("message"),
124 | containsString("has"),
125 | containsString("another")
126 | );
127 |
128 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
129 |
130 | assertThat(matches).isTrue();
131 | }
132 |
133 | @Test
134 | public void not_match_when_a_message_not_matches() {
135 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message has another message");
136 |
137 | ExpectedLoggingMessage expectedLoggingMessage = aLog().withMessage(containsString("message"),
138 | containsString("has"),
139 | containsString("NO_MATCH"),
140 | containsString("another")
141 | );
142 |
143 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
144 |
145 | assertThat(matches).isFalse();
146 | }
147 |
148 | @Test
149 | public void match_when_length_message_match() {
150 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message");
151 | ExpectedLoggingMessage expectedLoggingMessage = aLog()
152 | .length(equalTo(7));
153 |
154 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
155 |
156 | assertThat(matches).isTrue();
157 | }
158 |
159 | @Test
160 | public void match_when_mdc_keys_match() {
161 | MDC.put("aKey", "someValue");
162 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message");
163 | ExpectedLoggingMessage expectedLoggingMessage = aLog()
164 | .withMdc("aKey", equalTo("someValue"));
165 |
166 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
167 |
168 | assertThat(matches).isTrue();
169 | }
170 |
171 | @Test
172 | public void match_when_multiple_mdc_keys_match() {
173 | MDC.put("aKey", "someValue");
174 | MDC.put("anotherKey", "anotherValue");
175 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message");
176 | ExpectedLoggingMessage expectedLoggingMessage = aLog()
177 | .withMdc("aKey", equalTo("someValue"))
178 | .withMdc("anotherKey", equalTo("anotherValue"));
179 |
180 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
181 |
182 | assertThat(matches).isTrue();
183 | }
184 |
185 | @Test
186 | public void match_when_logger_class_match() {
187 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message");
188 | ExpectedLoggingMessage expectedLoggingMessage = aLog()
189 | .withLoggerName(equalTo(ExpectedLoggingMessageShould.class.getName()));
190 |
191 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
192 |
193 | assertThat(matches).isTrue();
194 | }
195 |
196 | @Test
197 | public void match_when_exception_class_match() {
198 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message", new RuntimeException());
199 | ExpectedLoggingMessage expectedLoggingMessage = aLog()
200 | .havingException(logException()
201 | .withException(instanceOf(RuntimeException.class)));
202 |
203 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
204 |
205 | assertThat(matches).isTrue();
206 | }
207 |
208 | @Test
209 | public void match_when_message_and_log_level_match() {
210 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message");
211 | ExpectedLoggingMessage expectedLoggingMessage = aLog()
212 | .withLevel(equalTo(INFO))
213 | .withMessage(equalTo("message"));
214 |
215 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
216 |
217 | assertThat(matches).isTrue();
218 | }
219 |
220 | @Test
221 | public void not_match_when_log_level_different() {
222 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message");
223 | ExpectedLoggingMessage expectedLoggingMessage = aLog()
224 | .withMessage(equalTo("differentMessage"));
225 |
226 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
227 |
228 | assertThat(matches).isFalse();
229 | }
230 |
231 | @Test
232 | public void not_match_when_message_different() {
233 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message");
234 | ExpectedLoggingMessage expectedLoggingMessage = aLog()
235 | .withMessage(equalTo("anotherMessage"));
236 |
237 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
238 |
239 | assertThat(matches).isFalse();
240 | }
241 |
242 | @Test
243 | public void not_match_when_message_length_different() {
244 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message");
245 | ExpectedLoggingMessage expectedLoggingMessage = aLog()
246 | .length(equalTo(8));
247 |
248 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
249 |
250 | assertThat(matches).isFalse();
251 | }
252 |
253 | @Test
254 | public void not_match_when_mdc_keys_different() {
255 | MDC.put("aKey", "differentValue");
256 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message");
257 | ExpectedLoggingMessage expectedLoggingMessage = aLog()
258 | .withMdc("aKey", equalTo("someValue"));
259 |
260 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
261 |
262 | MDC.clear();
263 | assertThat(matches).isFalse();
264 | }
265 |
266 | @Test
267 | public void not_match_when_a_mdc_keys_is_different() {
268 | MDC.put("aKey", "differentValue");
269 | MDC.put("anotherKey", "anotherValue");
270 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message");
271 | ExpectedLoggingMessage expectedLoggingMessage = aLog()
272 | .withMdc("aKey", equalTo("unmatchedValue"))
273 | .withMdc("anotherKey", equalTo("anotherValue"));
274 |
275 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
276 |
277 | MDC.clear();
278 | assertThat(matches).isFalse();
279 | }
280 |
281 | @Test
282 | public void not_match_when_logger_class_different() {
283 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message");
284 | ExpectedLoggingMessage expectedLoggingMessage = aLog()
285 | .withLoggerName(equalTo("anotherClassName"));
286 |
287 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
288 |
289 | assertThat(matches).isFalse();
290 | }
291 |
292 | @Test
293 | public void match_for_expected_marker() {
294 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message");
295 | logEvent.addMarker(MarkerFactory.getMarker("A_MARKER"));
296 |
297 | ExpectedLoggingMessage expectedLoggingMessage = aLog()
298 | .withMarker(MarkerFactory.getMarker("A_MARKER"));
299 |
300 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
301 |
302 | assertThat(matches).isTrue();
303 | }
304 |
305 | @Test
306 | public void match_for_expected_marker_label() {
307 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message");
308 | logEvent.addMarker(MarkerFactory.getMarker("A_MARKER"));
309 |
310 | ExpectedLoggingMessage expectedLoggingMessage = aLog()
311 | .withMarker("A_MARKER");
312 |
313 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
314 |
315 | assertThat(matches).isTrue();
316 | }
317 |
318 | @Test
319 | public void no_match_for_unexpected_marker() {
320 | LoggingEvent logEvent = aLoggingEventWith(INFO, "message");
321 | logEvent.addMarker(MarkerFactory.getMarker("A_MARKER"));
322 |
323 | ExpectedLoggingMessage expectedLoggingMessage = aLog()
324 | .withMarker("ANOTHER_MARKER");
325 |
326 | boolean matches = expectedLoggingMessage.matches(singletonList(logEvent));
327 |
328 | assertThat(matches).isFalse();
329 | }
330 |
331 | @Test
332 | public void describe_failure_using_to_string() {
333 | ExpectedLoggingMessage expectedLoggingMessage = aLog()
334 | .withLevel(equalTo(ERROR))
335 | .withMessage(equalTo("message"))
336 | .length(equalTo(8))
337 | .withMdc("aKey", equalTo("some"))
338 | .withMarker("A_MARKER")
339 | .withLoggerName(equalTo("className"))
340 | .havingException(logException()
341 | .withMessage(equalTo("exception thrown"))
342 | .withException(causeOf(IllegalArgumentException.class)));
343 |
344 | assertThat(expectedLoggingMessage.toString())
345 | .contains("logLevelMatcher=")
346 | .contains("markerMatcher=")
347 | .contains("expectedMessageMatcher=[\"message\"]")
348 | .contains("expectedLengthMatcher=<8>")
349 | .contains("expectedMdc={aKey=\"some\"}")
350 | .contains("expectedLoggerNameMatcher=\"className\"")
351 | .contains("expectedLoggedException=ExpectedLoggedException{expectedMessageMatcher=\"exception thrown\", expectedException=Expecting exception to be instance of class java.lang.IllegalArgumentException");
352 | }
353 |
354 | private LoggingEvent aLoggingEventWith(Level level, String message) {
355 | return aLoggingEventWith(level, message, null);
356 | }
357 |
358 | private LoggingEvent aLoggingEventWith(Level level, String message, Exception exception) {
359 | Logger log = (Logger) LoggerFactory.getLogger(this.getClass());
360 | return new LoggingEvent("fqcn", log, level, message, exception, null);
361 | }
362 | }
363 |
--------------------------------------------------------------------------------
/logcapture-core/src/test/java/org/logcapture/assertion/VerificationExceptionShould.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.assertion;
2 |
3 | import ch.qos.logback.classic.Level;
4 | import ch.qos.logback.classic.Logger;
5 | import ch.qos.logback.classic.spi.ILoggingEvent;
6 | import ch.qos.logback.classic.spi.LoggingEvent;
7 | import org.junit.Test;
8 | import org.slf4j.LoggerFactory;
9 |
10 | import java.util.Arrays;
11 | import java.util.HashMap;
12 | import java.util.List;
13 | import java.util.Map;
14 |
15 | import static org.logcapture.assertion.ExpectedLoggingMessage.aLog;
16 | import static java.util.Collections.emptyMap;
17 | import static org.assertj.core.api.Assertions.assertThat;
18 | import static org.hamcrest.Matchers.equalTo;
19 |
20 | public class VerificationExceptionShould {
21 |
22 | @Test
23 | public void containLogsEventsNotMatchingExpected() {
24 | ExpectedLoggingMessage expectedLogMessage = aLog().info().withMessage("a log message");
25 | List logEvents = Arrays.asList(
26 | aLoggingEventWith(Level.INFO, "a different message"),
27 | aLoggingEventWith(Level.INFO, "another different message")
28 | );
29 |
30 | VerificationException verificationException = VerificationException.forUnmatchedLog(expectedLogMessage, logEvents);
31 |
32 | assertThat(verificationException.toString()).isEqualTo("org.logcapture.assertion.VerificationException: Expected matching: \n" +
33 | "ExpectedLoggingMessage{logLevelMatcher=, expectedMessageMatcher=[\"a log message\"], expectedMdc={}}\n" +
34 | "Logs received: \n" +
35 | "level: INFO marker: null mdc: {} message: a different message\n" +
36 | "level: INFO marker: null mdc: {} message: another different message");
37 | }
38 |
39 | @Test
40 | public void containLogsEventsNotMatchingExpectedWithMdcKeys() {
41 | Map mdcKeys = new HashMap() {{
42 | put("aKey", "aValue");
43 | put("anotherKey", "anotherValue");
44 | }};
45 |
46 | ExpectedLoggingMessage expectedLogMessage = aLog().info()
47 | .withMessage("a log message")
48 | .withMdc("aKey", equalTo("aValue"))
49 | .withMdc("anotherKey", equalTo("anotherValue"));
50 |
51 | List logEvents = Arrays.asList(
52 | aLoggingEventWith(Level.INFO, "a different message", mdcKeys),
53 | aLoggingEventWith(Level.INFO, "another different message", mdcKeys)
54 | );
55 |
56 | VerificationException verificationException = VerificationException.forUnmatchedLog(expectedLogMessage, logEvents);
57 |
58 | assertThat(verificationException.toString()).isEqualTo("org.logcapture.assertion.VerificationException: Expected matching: \n" +
59 | "ExpectedLoggingMessage{logLevelMatcher=, expectedMessageMatcher=[\"a log message\"], expectedMdc={anotherKey=\"anotherValue\", aKey=\"aValue\"}}\n" +
60 | "Logs received: \n" +
61 | "level: INFO marker: null mdc: {anotherKey=anotherValue, aKey=aValue} message: a different message\n" +
62 | "level: INFO marker: null mdc: {anotherKey=anotherValue, aKey=aValue} message: another different message");
63 | }
64 |
65 | private LoggingEvent aLoggingEventWith(Level level, String message) {
66 | return aLoggingEventWith(level, message, emptyMap());
67 | }
68 |
69 | private LoggingEvent aLoggingEventWith(Level level, String message, Map mdcKeys) {
70 | Logger log = (Logger) LoggerFactory.getLogger(getClass());
71 | LoggingEvent logEvent = new LoggingEvent("fqcn", log, level, message, null, null);
72 | logEvent.setMDCPropertyMap(mdcKeys);
73 | return logEvent;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/logcapture-core/src/test/java/org/logcapture/matcher/ExpectedExceptionMatcherShould.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.matcher;
2 |
3 | import ch.qos.logback.classic.spi.ThrowableProxy;
4 | import org.junit.Test;
5 |
6 | import static org.assertj.core.api.Assertions.assertThat;
7 | import static org.hamcrest.core.Is.isA;
8 |
9 | public class ExpectedExceptionMatcherShould {
10 |
11 | @Test
12 | public void match_when_ThrowableProxy_contains_exception() {
13 | ExpectedExceptionMatcher expectedExceptionMatcher = new ExpectedExceptionMatcher(isA(RuntimeException.class));
14 |
15 | boolean matches = expectedExceptionMatcher.matches(new ThrowableProxy(new RuntimeException()));
16 |
17 | assertThat(matches).isTrue();
18 | }
19 |
20 | @Test
21 | public void not_match_when_is_not_ThrowableProxy() {
22 | ExpectedExceptionMatcher expectedExceptionMatcher = new ExpectedExceptionMatcher(isA(RuntimeException.class));
23 |
24 | boolean matches = expectedExceptionMatcher.matches(new RuntimeException());
25 |
26 | assertThat(matches).isFalse();
27 | }
28 |
29 | @Test
30 | public void not_match_when_ThrowableProxy_contains_another_exception() {
31 | ExpectedExceptionMatcher expectedExceptionMatcher = new ExpectedExceptionMatcher(isA(IllegalArgumentException.class));
32 |
33 | boolean matches = expectedExceptionMatcher.matches(new ThrowableProxy(new IllegalStateException()));
34 |
35 | assertThat(matches).isFalse();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/logcapture-core/src/test/java/org/logcapture/matcher/exception/ExceptionCauseMatcherShould.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.matcher.exception;
2 |
3 | import org.hamcrest.Matcher;
4 | import org.hamcrest.StringDescription;
5 | import org.junit.Test;
6 |
7 | import static org.logcapture.matcher.exception.ExceptionCauseMatcher.causeOf;
8 | import static org.assertj.core.api.Assertions.assertThat;
9 |
10 | public class ExceptionCauseMatcherShould {
11 |
12 | @Test
13 | public void match_when_cause_matches() {
14 | Matcher matcher = causeOf(IllegalStateException.class);
15 |
16 | assertThat(matcher.matches(new Throwable(new IllegalStateException()))).isTrue();
17 | }
18 |
19 | @Test
20 | public void not_match_when_cause_different() {
21 | Matcher matcher = causeOf(IllegalStateException.class);
22 |
23 | assertThat(matcher.matches(new Throwable(new IllegalArgumentException()))).isFalse();
24 | }
25 |
26 | @Test
27 | public void description_adds_context() {
28 | Matcher matcher = causeOf(RuntimeException.class);
29 | StringDescription description = new StringDescription();
30 |
31 | matcher.describeTo(description);
32 |
33 | assertThat(description.toString()).isEqualTo("Expecting exception to be instance of class java.lang.RuntimeException");
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/logcapture-core/src/test/java/org/logcapture/matcher/exception/ExceptionCauseMessageMatcherShould.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.matcher.exception;
2 |
3 | import org.hamcrest.Matcher;
4 | import org.hamcrest.StringDescription;
5 | import org.junit.Test;
6 |
7 | import static org.logcapture.matcher.exception.ExceptionCauseMessageMatcher.whereCauseMessage;
8 | import static org.assertj.core.api.Assertions.assertThat;
9 | import static org.hamcrest.CoreMatchers.equalTo;
10 |
11 | public class ExceptionCauseMessageMatcherShould {
12 |
13 | @Test
14 | public void match_when_message_matches() {
15 | Matcher matcher = whereCauseMessage(equalTo("message"));
16 |
17 | assertThat(matcher.matches(new Throwable(new RuntimeException("message")))).isTrue();
18 | }
19 |
20 | @Test
21 | public void not_match_when_message_different() {
22 | Matcher matcher = whereCauseMessage(equalTo("message"));
23 |
24 | assertThat(matcher.matches(new Throwable(new RuntimeException("different message")))).isFalse();
25 | }
26 |
27 | @Test
28 | public void description_adds_context() {
29 | Matcher matcher = whereCauseMessage(equalTo("message"));
30 | StringDescription description = new StringDescription();
31 |
32 | matcher.describeTo(description);
33 |
34 | assertThat(description.toString()).isEqualTo("Expecting exception cause to contain \"message\"");
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/logcapture-example/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | dependencies {
3 | implementation "org.logcapture:logcapture-junit4:1.3.6"
4 | implementation "junit:junit:4.13.2"
5 | }
6 |
--------------------------------------------------------------------------------
/logcapture-example/src/test/java/org/logcapture/example/ExampleShould.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.example;
2 |
3 | import org.logcapture.junit4.LogCaptureRule;
4 | import org.junit.Rule;
5 | import org.junit.Test;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 | import org.slf4j.MDC;
9 | import org.slf4j.MarkerFactory;
10 |
11 | import static ch.qos.logback.classic.Level.INFO;
12 | import static org.logcapture.assertion.ExpectedLoggedException.logException;
13 | import static org.logcapture.assertion.ExpectedLoggingMessage.aLog;
14 | import static org.logcapture.matcher.exception.ExceptionCauseMatcher.causeOf;
15 | import static org.logcapture.matcher.exception.ExceptionCauseMessageMatcher.whereCauseMessage;
16 | import static org.hamcrest.Matchers.containsString;
17 | import static org.hamcrest.Matchers.equalTo;
18 | import static org.hamcrest.Matchers.isA;
19 |
20 | public class ExampleShould {
21 | private final Logger log = LoggerFactory.getLogger(ExampleShould.class);
22 |
23 | @Rule
24 | public LogCaptureRule logCaptureRule = new LogCaptureRule();
25 |
26 | private ServiceThatLogs underTest = new ServiceThatLogs();
27 |
28 | @Test
29 | public void verify_captured_events() {
30 | underTest.methodThatLogsStuff();
31 | logCaptureRule.logged(aLog().info()
32 | .withMessage("a message"));
33 | }
34 |
35 | @Test
36 | public void verify_captured_events_with_marker() {
37 | log.info(MarkerFactory.getMarker("a_marker"), "a message");
38 | logCaptureRule.logged(aLog()
39 | .withLevel(equalTo(INFO))
40 | .withMarker("a_marker")
41 | .withMessage(equalTo("a message")));
42 | }
43 |
44 | @Test
45 | public void verify_captured_events_with_exception() {
46 | RuntimeException exception = new RuntimeException();
47 |
48 | underTest.methodThatLogs(exception);
49 | logCaptureRule.logged(aLog()
50 | .havingException(logException()
51 | .withException(isA(RuntimeException.class))
52 | ));
53 | }
54 |
55 | @Test
56 | public void verify_captured_events_with_exception_cause_message() {
57 | RuntimeException exception = new RuntimeException(new IllegalStateException("Some state is invalid"));
58 | underTest.methodThatLogs(exception);
59 | logCaptureRule.logged(aLog()
60 | .havingException(logException()
61 | .withException(whereCauseMessage(containsString("state is invalid")))
62 | ));
63 | }
64 |
65 | @Test
66 | public void verify_captured_events_with_exception_cause() {
67 | RuntimeException exception = new RuntimeException(new IllegalStateException("Some state is invalid"));
68 | underTest.methodThatLogs(exception);
69 | logCaptureRule.logged(aLog()
70 | .havingException(logException()
71 | .withException(causeOf(IllegalStateException.class))
72 | ));
73 | }
74 |
75 | @Test
76 | public void verify_mdc_keys() {
77 | MDC.put("aKey", "someValue");
78 |
79 | underTest.methodThatLogsStuff();
80 | logCaptureRule.logged(aLog().info()
81 | .withMdc("aKey", equalTo("someValue"))
82 | .withMessage("a message"));
83 | }
84 |
85 | @Test
86 | public void verify_captured_n_logs() {
87 | log.info("a message");
88 | log.info("a message");
89 | log.info("what");
90 |
91 | logCaptureRule.logged(aLog().info()
92 | .withMessage("a message"), 2);
93 | }
94 |
95 | class ServiceThatLogs {
96 |
97 | void methodThatLogsStuff() {
98 | log.info("a message");
99 | log.info("another message");
100 | }
101 |
102 | void methodThatLogs(Exception exception) {
103 | log.error("message", exception);
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/logcapture-junit4/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java'
3 | }
4 |
5 | dependencies {
6 | api project(":logcapture-core")
7 | implementation "junit:junit:4.13.2"
8 |
9 | testImplementation "org.assertj:assertj-core:3.27.3"
10 | }
11 |
--------------------------------------------------------------------------------
/logcapture-junit4/src/main/java/org/logcapture/junit4/LogCaptureRule.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.junit4;
2 |
3 | import ch.qos.logback.classic.Logger;
4 | import ch.qos.logback.classic.spi.ILoggingEvent;
5 | import org.hamcrest.Matcher;
6 | import org.junit.rules.MethodRule;
7 | import org.junit.rules.TestRule;
8 | import org.junit.runner.Description;
9 | import org.junit.runners.model.FrameworkMethod;
10 | import org.junit.runners.model.Statement;
11 | import org.logcapture.LogCapture;
12 | import org.logcapture.logback.StubAppender;
13 | import org.slf4j.LoggerFactory;
14 |
15 | import java.util.List;
16 |
17 | import static org.slf4j.Logger.ROOT_LOGGER_NAME;
18 |
19 | public class LogCaptureRule implements MethodRule, TestRule {
20 |
21 | private final String loggerName;
22 | private StubAppender logAppender;
23 |
24 | public LogCaptureRule() {
25 | this(ROOT_LOGGER_NAME);
26 | }
27 |
28 | public LogCaptureRule(String loggerName) {
29 | this.loggerName = loggerName;
30 | }
31 |
32 | @Override
33 | public Statement apply(Statement base, Description description) {
34 | return apply(base, null, null);
35 | }
36 |
37 | @Override
38 | public Statement apply(Statement base, FrameworkMethod method, Object target) {
39 | return new Statement() {
40 | @Override
41 | public void evaluate() throws Throwable {
42 | logAppender = new StubAppender();
43 | Logger root = (Logger) LoggerFactory.getLogger(loggerName);
44 |
45 | root.addAppender(logAppender);
46 | try {
47 | base.evaluate();
48 | } finally {
49 | root.detachAppender(logAppender);
50 | }
51 | }
52 | };
53 | }
54 |
55 | public LogCapture logged(Matcher> expectedLoggingMessage) {
56 | return new LogCapture<>(logAppender.events()).logged(expectedLoggingMessage);
57 | }
58 |
59 | public LogCapture logged(Matcher> expectedLoggingMessage, Integer times) {
60 | return new LogCapture<>(logAppender.events()).logged(expectedLoggingMessage, times);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/logcapture-junit4/src/test/java/org/logcapture/junit4/LogCaptureClassRuleShould.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.junit4;
2 |
3 | import org.junit.ClassRule;
4 | import org.junit.Test;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import static org.logcapture.assertion.ExpectedLoggingMessage.aLog;
9 |
10 | public class LogCaptureClassRuleShould {
11 | private final Logger log = LoggerFactory.getLogger(LogCaptureClassRuleShould.class);
12 |
13 | @ClassRule
14 | public static LogCaptureRule logCaptureRule = new LogCaptureRule();
15 |
16 | @Test
17 | public void verify_sync_logs_using_rule() {
18 | log.info("a message");
19 |
20 | logCaptureRule.logged(aLog().info().withMessage("a message"));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/logcapture-junit4/src/test/java/org/logcapture/junit4/LogCaptureRuleShould.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.junit4;
2 |
3 | import org.junit.Rule;
4 | import org.junit.Test;
5 | import org.logcapture.assertion.VerificationException;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 | import org.slf4j.MDC;
9 | import org.slf4j.MarkerFactory;
10 |
11 | import static ch.qos.logback.classic.Level.DEBUG;
12 | import static ch.qos.logback.classic.Level.INFO;
13 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
14 | import static org.hamcrest.Matchers.*;
15 | import static org.logcapture.assertion.ExpectedLoggingMessage.aLog;
16 |
17 | public class LogCaptureRuleShould {
18 | private static final String LOG_NAME = "aLogNotAttachedToRoot";
19 | private final Logger log = LoggerFactory.getLogger(LogCaptureRuleShould.class);
20 |
21 | @Rule
22 | public LogCaptureRule logCaptureRule = new LogCaptureRule();
23 |
24 | @Rule
25 | public LogCaptureRule logCaptureRuleAttached = new LogCaptureRule(LOG_NAME);
26 |
27 | @Test
28 | public void verify_missing_events() {
29 | logCaptureRule.logged(not(aLog()
30 | .withLevel(equalTo(INFO))
31 | .withMessage(equalTo("missing message"))));
32 | }
33 |
34 | @Test
35 | public void verify_multiple_events() {
36 | log.info("first message");
37 | log.debug("second message");
38 | logCaptureRule.logged(allOf(
39 | aLog().withLevel(equalTo(INFO)).withMessage(equalTo("first message")),
40 | aLog().withLevel(equalTo(DEBUG)).withMessage(equalTo("second message"))
41 | ));
42 | }
43 |
44 | @Test
45 | public void verify_sync_logs_using_rule() {
46 | log.info("a message");
47 |
48 | logCaptureRule.logged(aLog().info().withMessage("a message"));
49 | }
50 |
51 | @Test
52 | public void verify_captured_events_with_marker() {
53 | log.info(MarkerFactory.getMarker("a_marker"), "a message");
54 | logCaptureRule.logged(aLog()
55 | .withLevel(equalTo(INFO))
56 | .withMarker("a_marker")
57 | .withMessage(equalTo("a message")));
58 | }
59 |
60 | @Test
61 | public void verify_log_when_is_not_in_root() {
62 | Logger logNotInRoot = createLogger(LOG_NAME);
63 |
64 | logNotInRoot.info("a message");
65 |
66 | assertThatExceptionOfType(VerificationException.class)
67 | .isThrownBy(() -> logCaptureRule.logged(aLog().info().withMessage("a message")));
68 |
69 | logCaptureRuleAttached.logged(aLog().info().withMessage("a message"));
70 | }
71 |
72 | @Test
73 | public void verify_log_with_mdc_keys() {
74 | MDC.put("a-key", "a-value");
75 | log.info("a message");
76 |
77 | logCaptureRule.logged(aLog()
78 | .info()
79 | .withMdc("a-key", "a-value")
80 | .withMessage("a message"));
81 | }
82 |
83 | @Test
84 | public void verify_log_n_times() {
85 | log.info("a message");
86 | log.info("a message");
87 |
88 | logCaptureRule.logged(aLog().info().withMessage("a message"), 2);
89 | }
90 |
91 | private Logger createLogger(String name) {
92 | ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(name);
93 | logger.setLevel(DEBUG);
94 | logger.setAdditive(false);
95 | return logger;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/logcapture-junit5/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java'
3 | }
4 |
5 | dependencies {
6 | api project(":logcapture-core")
7 |
8 | implementation(platform('org.junit:junit-bom:5.12.2'))
9 | implementation "org.junit.jupiter:junit-jupiter-api"
10 |
11 | testImplementation "org.junit.jupiter:junit-jupiter-engine"
12 | testImplementation "org.junit.platform:junit-platform-launcher"
13 | }
14 |
15 | test {
16 | useJUnitPlatform()
17 | }
18 |
--------------------------------------------------------------------------------
/logcapture-junit5/src/main/java/org/logcapture/junit5/LogCaptureExtension.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.junit5;
2 |
3 | import ch.qos.logback.classic.Logger;
4 | import ch.qos.logback.classic.spi.ILoggingEvent;
5 | import org.hamcrest.Matcher;
6 | import org.junit.jupiter.api.extension.AfterEachCallback;
7 | import org.junit.jupiter.api.extension.BeforeEachCallback;
8 | import org.junit.jupiter.api.extension.ExtensionContext;
9 | import org.logcapture.LogCapture;
10 | import org.logcapture.logback.StubAppender;
11 | import org.slf4j.LoggerFactory;
12 |
13 | import java.util.List;
14 |
15 | import static org.slf4j.Logger.ROOT_LOGGER_NAME;
16 |
17 | public class LogCaptureExtension implements BeforeEachCallback, AfterEachCallback {
18 |
19 | private final String loggerName;
20 | private StubAppender logAppender;
21 | private Logger root;
22 |
23 | public LogCaptureExtension() {
24 | this(ROOT_LOGGER_NAME);
25 | }
26 |
27 | public LogCaptureExtension(String loggerName) {
28 | this.loggerName = loggerName;
29 | }
30 |
31 | @Override
32 | public void beforeEach(ExtensionContext context) {
33 | logAppender = new StubAppender();
34 | root = (Logger) LoggerFactory.getLogger(loggerName);
35 |
36 | root.addAppender(logAppender);
37 | }
38 |
39 | @Override
40 | public void afterEach(ExtensionContext context) {
41 | root.detachAppender(logAppender);
42 | }
43 |
44 | public LogCapture logged(Matcher> expectedLoggingMessage) {
45 | return new LogCapture<>(logAppender.events()).logged(expectedLoggingMessage);
46 | }
47 |
48 | public LogCapture logged(Matcher> expectedLoggingMessage, Integer times) {
49 | return new LogCapture<>(logAppender.events()).logged(expectedLoggingMessage, times);
50 | }
51 |
52 | public List getEvents() {
53 | return logAppender.events();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/logcapture-junit5/src/test/java/org/logcapture/junit5/LogCaptureRegisterExtensionShould.java:
--------------------------------------------------------------------------------
1 | package org.logcapture.junit5;
2 |
3 | import org.hamcrest.Matchers;
4 | import org.junit.jupiter.api.Test;
5 | import org.junit.jupiter.api.extension.RegisterExtension;
6 | import org.logcapture.assertion.ExpectedLoggingMessage;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 |
10 | import static ch.qos.logback.classic.Level.DEBUG;
11 | import static ch.qos.logback.classic.Level.INFO;
12 | import static org.hamcrest.Matchers.equalTo;
13 | import static org.logcapture.assertion.ExpectedLoggingMessage.aLog;
14 |
15 | class LogCaptureRegisterExtensionShould {
16 | private final Logger log = LoggerFactory.getLogger(LogCaptureRegisterExtensionShould.class);
17 |
18 | @RegisterExtension
19 | LogCaptureExtension logCaptureExtension = new LogCaptureExtension();
20 |
21 | @Test
22 | void verify_missing_events() {
23 | logCaptureExtension.logged(Matchers.not(ExpectedLoggingMessage.aLog()
24 | .withLevel(equalTo(INFO))
25 | .withMessage(equalTo("missing message"))));
26 | }
27 |
28 | @Test
29 | void verify_multiple_events() {
30 | log.info("first message");
31 | log.debug("second message");
32 | logCaptureExtension.logged(
33 | Matchers.allOf(
34 | ExpectedLoggingMessage.aLog().withLevel(equalTo(INFO)).withMessage(equalTo("first message")),
35 | ExpectedLoggingMessage.aLog().withLevel(equalTo(DEBUG)).withMessage(equalTo("second message"))
36 | )
37 | );
38 | }
39 |
40 | @Test
41 | void verify_sync_logs_using_rule() {
42 | log.info("a message");
43 |
44 | logCaptureExtension.logged(ExpectedLoggingMessage.aLog().info().withMessage("a message"));
45 | }
46 |
47 | @Test
48 | void verify_log_n_times() {
49 | log.info("a message");
50 | log.info("a message");
51 |
52 | logCaptureExtension.logged(aLog().info().withMessage("a message"), 2);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/logcapture-kotest/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'org.jetbrains.kotlin.jvm' version '2.1.20'
3 | }
4 |
5 | ext {
6 | kotestVersion = '5.9.1'
7 | }
8 |
9 | dependencies {
10 | api project(":logcapture-core")
11 |
12 | implementation "io.kotest:kotest-runner-junit5-jvm:$kotestVersion"
13 | implementation "io.kotest:kotest-assertions-core-jvm:$kotestVersion"
14 | }
15 |
16 | test {
17 | useJUnitPlatform()
18 | }
19 |
--------------------------------------------------------------------------------
/logcapture-kotest/src/main/kotlin/org/logcapture/kotest/LogCaptureListener.kt:
--------------------------------------------------------------------------------
1 | package org.logcapture.kotest
2 |
3 | import ch.qos.logback.classic.Logger
4 | import ch.qos.logback.classic.spi.ILoggingEvent
5 | import org.logcapture.LogCapture
6 | import org.logcapture.logback.StubAppender
7 | import io.kotest.core.listeners.TestListener
8 | import io.kotest.core.test.TestCase
9 | import io.kotest.core.test.TestResult
10 | import org.hamcrest.Matcher
11 | import org.slf4j.Logger.ROOT_LOGGER_NAME
12 | import org.slf4j.LoggerFactory
13 |
14 | class LogCaptureListener(private val loggerName: String = ROOT_LOGGER_NAME) : TestListener {
15 |
16 | private lateinit var logAppender: StubAppender
17 | private lateinit var root: Logger
18 |
19 | override suspend fun beforeTest(testCase: TestCase) {
20 | logAppender = StubAppender()
21 | root = LoggerFactory.getLogger(loggerName) as Logger
22 | root.addAppender(logAppender)
23 | }
24 |
25 | override suspend fun afterTest(testCase: TestCase, result: TestResult) {
26 | root.detachAppender(logAppender)
27 | }
28 |
29 | fun logged(expectedLoggingMessage: Matcher>): LogCapture {
30 | return LogCapture(logAppender.events()).logged(expectedLoggingMessage)
31 | }
32 |
33 | fun logged(expectedLoggingMessage: Matcher>, times: Int): LogCapture {
34 | return LogCapture(logAppender.events()).logged(expectedLoggingMessage, times)
35 | }
36 |
37 | fun logged(): LogCapture {
38 | return LogCapture(logAppender.events())
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/logcapture-kotest/src/test/kotlin/org/logcapture/kotest/LogCaptureListenerSpec.kt:
--------------------------------------------------------------------------------
1 | package org.logcapture.kotest
2 |
3 | import ch.qos.logback.classic.Level
4 | import io.kotest.assertions.timing.eventually
5 | import io.kotest.core.spec.style.StringSpec
6 | import io.kotest.inspectors.forOne
7 | import io.kotest.matchers.shouldBe
8 | import io.kotest.matchers.string.shouldContain
9 | import org.logcapture.assertion.ExpectedLoggingMessage.aLog
10 | import org.slf4j.Logger
11 | import org.slf4j.LoggerFactory
12 | import kotlin.time.Duration.Companion.seconds
13 |
14 | class LogCaptureListenerSpec : StringSpec({
15 |
16 | val log: Logger = LoggerFactory.getLogger(LogCaptureListenerSpec::class.java)
17 |
18 | val logCaptureListener = LogCaptureListener()
19 | listener(logCaptureListener)
20 |
21 | "verify log messages" {
22 | log.info("a message")
23 |
24 | logCaptureListener.logged(aLog().info().withMessage("a message"))
25 | }
26 |
27 | "verify log n times " {
28 | log.info("a message")
29 | log.info("a message")
30 |
31 | logCaptureListener.logged(aLog().info().withMessage("a message"), 2)
32 | }
33 |
34 | "use eventually to verify logs" {
35 | var i = 0
36 | eventually(1.seconds) {
37 | i += 1
38 | logMessageWhenCondition(log, i == 5)
39 | logCaptureListener.logged(aLog().info().withMessage("a message"))
40 | }
41 | }
42 |
43 | "verify using kotest assertions" {
44 | log.info("a message")
45 |
46 | logCaptureListener.logged().events.forOne {
47 | it.level shouldBe Level.INFO
48 | it.message shouldContain "message"
49 | }
50 | }
51 | })
52 |
53 | fun logMessageWhenCondition(log: Logger, condition: Boolean) {
54 | if (condition) {
55 | log.info("a message")
56 | }
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/logcapture-kotest/src/test/kotlin/org/logcapture/kotest/LogCaptureListenerWithCustomLoggerNameSpec.kt:
--------------------------------------------------------------------------------
1 | package org.logcapture.kotest
2 |
3 | import org.logcapture.assertion.ExpectedLoggingMessage.aLog
4 | import io.kotest.core.spec.style.StringSpec
5 | import org.hamcrest.Matchers.equalTo
6 | import org.slf4j.Logger
7 | import org.slf4j.LoggerFactory
8 |
9 | class LogCaptureListenerWithCustomLoggerNameSpec : StringSpec({
10 |
11 | val log: Logger = LoggerFactory.getLogger("CUSTOM-LOGGER")
12 |
13 | val logCaptureListener = LogCaptureListener("CUSTOM-LOGGER")
14 | listener(logCaptureListener)
15 |
16 | "verify log messages" {
17 | log.info("a message")
18 |
19 | logCaptureListener.logged(aLog().info().withLoggerName(equalTo("CUSTOM-LOGGER")))
20 | logCaptureListener.logged(aLog().info().withMessage("a message"))
21 | }
22 | })
23 |
--------------------------------------------------------------------------------
/logcapture-spock2/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'groovy'
3 | }
4 |
5 | dependencies {
6 | api project(":logcapture-core")
7 |
8 | implementation 'org.spockframework:spock-core:2.3-groovy-4.0'
9 | }
10 |
11 | test {
12 | useJUnitPlatform()
13 | }
14 |
--------------------------------------------------------------------------------
/logcapture-spock2/src/main/groovy/org/logcapture/spock2/LogCaptureSpec.groovy:
--------------------------------------------------------------------------------
1 | package org.logcapture.spock2
2 |
3 | import ch.qos.logback.classic.Logger
4 | import ch.qos.logback.classic.spi.ILoggingEvent
5 | import org.hamcrest.Matcher
6 | import org.logcapture.LogCapture
7 | import org.logcapture.logback.StubAppender
8 | import org.slf4j.LoggerFactory
9 | import spock.lang.Specification
10 |
11 | import static org.slf4j.Logger.ROOT_LOGGER_NAME
12 |
13 | class LogCaptureSpec extends Specification {
14 |
15 | def root
16 | def logAppender
17 |
18 | def setup() {
19 | logAppender = new StubAppender()
20 | root = (Logger) LoggerFactory.getLogger(ROOT_LOGGER_NAME)
21 |
22 | root.addAppender(logAppender)
23 | }
24 |
25 | def cleanup() {
26 | root.detachAppender(logAppender)
27 | }
28 |
29 | LogCapture logged(Matcher> expectedLoggingMessage) {
30 | return new LogCapture<>(logAppender.events()).logged(expectedLoggingMessage)
31 | }
32 |
33 | LogCapture logged(Matcher> expectedLoggingMessage, Integer times) {
34 | return new LogCapture<>(logAppender.events()).logged(expectedLoggingMessage, times)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/logcapture-spock2/src/main/groovy/org/logcapture/spock2/LogCaptureTrait.groovy:
--------------------------------------------------------------------------------
1 | package org.logcapture.spock2
2 |
3 | import ch.qos.logback.classic.Logger
4 | import ch.qos.logback.classic.spi.ILoggingEvent
5 | import org.hamcrest.Matcher
6 | import org.logcapture.LogCapture
7 | import org.logcapture.logback.StubAppender
8 | import org.slf4j.LoggerFactory
9 |
10 | trait LogCaptureTrait {
11 |
12 | def root
13 | def logAppender
14 |
15 | def setup() {
16 | logAppender = new StubAppender()
17 | root = (Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME)
18 |
19 | root.addAppender(logAppender)
20 | }
21 |
22 | def cleanup() {
23 | root.detachAppender(logAppender)
24 | }
25 |
26 | LogCapture logged(Matcher> expectedLoggingMessage) {
27 | return new LogCapture<>(logAppender.events()).logged(expectedLoggingMessage)
28 | }
29 |
30 | LogCapture logged(Matcher> expectedLoggingMessage, Integer times) {
31 | return new LogCapture<>(logAppender.events()).logged(expectedLoggingMessage, times)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/logcapture-spock2/src/test/groovy/org/logcapture/spock2/LogCaptureSpecShould.groovy:
--------------------------------------------------------------------------------
1 | package org.logcapture.spock2
2 |
3 |
4 | import org.slf4j.Logger
5 | import org.slf4j.LoggerFactory
6 | import spock.lang.Shared
7 |
8 | import static ch.qos.logback.classic.Level.DEBUG
9 | import static ch.qos.logback.classic.Level.INFO
10 | import static org.hamcrest.Matchers.*
11 | import static org.logcapture.assertion.ExpectedLoggingMessage.aLog
12 |
13 | class LogCaptureSpecShould extends LogCaptureSpec {
14 |
15 | @Shared
16 | Logger log = LoggerFactory.getLogger(getClass())
17 |
18 | def "verify missing events"() {
19 | expect:
20 | logged(not(aLog()
21 | .withLevel(equalTo(INFO))
22 | .withMessage(equalTo("missing message"))))
23 | }
24 |
25 | def "verify multiple events"() {
26 | expect:
27 | log.info("first message")
28 | log.debug("second message")
29 | logged allOf(aLog().withLevel(INFO).withMessage("first message"),
30 | aLog().withLevel(DEBUG).withMessage("second message"))
31 | }
32 |
33 | def "verify sync logs using rule"() {
34 | expect:
35 | log.info("a message")
36 |
37 | logged(aLog().info().withMessage("a message"))
38 | }
39 |
40 | def "verify n times logs"() {
41 | expect:
42 | log.info("a message")
43 | log.info("a message")
44 |
45 | logged(aLog().info().withMessage("a message"), 2)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/logcapture-spock2/src/test/groovy/org/logcapture/spock2/LogCaptureTraitShould.groovy:
--------------------------------------------------------------------------------
1 | package org.logcapture.spock2
2 |
3 | import org.slf4j.Logger
4 | import org.slf4j.LoggerFactory
5 | import spock.lang.Shared
6 | import spock.lang.Specification
7 |
8 | import static ch.qos.logback.classic.Level.DEBUG
9 | import static ch.qos.logback.classic.Level.INFO
10 | import static org.hamcrest.Matchers.*
11 | import static org.logcapture.assertion.ExpectedLoggingMessage.aLog
12 |
13 | class LogCaptureTraitShould extends Specification implements LogCaptureTrait {
14 |
15 | @Shared
16 | Logger log = LoggerFactory.getLogger(LogCaptureTraitShould.class)
17 |
18 | def "verify missing events"() {
19 | expect:
20 | logged(not(aLog()
21 | .withLevel(equalTo(INFO))
22 | .withMessage(equalTo("missing message"))))
23 | }
24 |
25 | def "verify multiple events"() {
26 | expect:
27 | log.info("first message")
28 | log.debug("second message")
29 | logged(allOf(
30 | aLog().withLevel(INFO).withMessage("first message"),
31 | aLog().withLevel(DEBUG).withMessage("second message"))
32 | )
33 | }
34 |
35 | def "verify sync logs using rule"() {
36 | expect:
37 | log.info("a message")
38 |
39 | logged(aLog().info().withMessage("a message"))
40 | }
41 |
42 | def "verify n times logs"() {
43 | expect:
44 | log.info("a message")
45 | log.info("a message")
46 |
47 | logged(aLog().info().withMessage("a message"), 2)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.gradle.develocity") version "4.0.1"
3 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.10.0"
4 | }
5 |
6 | develocity {
7 | buildScan {
8 | termsOfUseUrl.set("https://gradle.com/help/legal-terms-of-use")
9 | termsOfUseAgree.set("yes")
10 | }
11 | }
12 |
13 |
14 | include 'logcapture-core',
15 | 'logcapture-junit4',
16 | 'logcapture-junit5',
17 | 'logcapture-spock2',
18 | 'logcapture-kotest',
19 | 'logcapture-example'
20 |
21 | rootProject.name = 'logcapture'
22 |
23 |
--------------------------------------------------------------------------------