Thanks for using Compiler Explorer
Sponsors
Jakt
C++
Ada
Algol68
Analysis
Android Java
Android Kotlin
Assembly
C
C3
Carbon
C with Coccinelle
C++ with Coccinelle
C++ (Circle)
CIRCT
Clean
Clojure
CMake
CMakeScript
COBOL
C++ for OpenCL
MLIR
Cppx
Cppx-Blue
Cppx-Gold
Cpp2-cppfront
Crystal
C#
CUDA C++
D
Dart
Elixir
Erlang
Fortran
F#
GLSL
Go
Haskell
HLSL
Helion
Hook
Hylo
IL
ispc
Java
Julia
Kotlin
LLVM IR
LLVM MIR
Modula-2
Mojo
Nim
Numba
Nix
Objective-C
Objective-C++
OCaml
Odin
OpenCL C
Pascal
Pony
PTX
Python
Racket
Raku
Ruby
Rust
Sail
Snowball
Scala
Slang
Solidity
Spice
SPIR-V
Swift
LLVM TableGen
Toit
Triton
TypeScript Native
V
Vala
Visual Basic
Vyper
WASM
Yul (Solidity IR)
Zig
Javascript
GIMPLE
Ygen
sway
c++ source #1
Output
Compile to binary object
Link to binary
Execute the code
Intel asm syntax
Demangle identifiers
Verbose demangling
Filters
Unused labels
Library functions
Directives
Comments
Horizontal whitespace
Debug intrinsics
Compiler
6502-c++ 11.1.0
ARM GCC 10.2.0
ARM GCC 10.3.0
ARM GCC 10.4.0
ARM GCC 10.5.0
ARM GCC 11.1.0
ARM GCC 11.2.0
ARM GCC 11.3.0
ARM GCC 11.4.0
ARM GCC 12.1.0
ARM GCC 12.2.0
ARM GCC 12.3.0
ARM GCC 12.4.0
ARM GCC 12.5.0
ARM GCC 13.1.0
ARM GCC 13.2.0
ARM GCC 13.2.0 (unknown-eabi)
ARM GCC 13.3.0
ARM GCC 13.3.0 (unknown-eabi)
ARM GCC 13.4.0
ARM GCC 13.4.0 (unknown-eabi)
ARM GCC 14.1.0
ARM GCC 14.1.0 (unknown-eabi)
ARM GCC 14.2.0
ARM GCC 14.2.0 (unknown-eabi)
ARM GCC 14.3.0
ARM GCC 14.3.0 (unknown-eabi)
ARM GCC 15.1.0
ARM GCC 15.1.0 (unknown-eabi)
ARM GCC 15.2.0
ARM GCC 15.2.0 (unknown-eabi)
ARM GCC 4.5.4
ARM GCC 4.6.4
ARM GCC 5.4
ARM GCC 6.3.0
ARM GCC 6.4.0
ARM GCC 7.3.0
ARM GCC 7.5.0
ARM GCC 8.2.0
ARM GCC 8.5.0
ARM GCC 9.3.0
ARM GCC 9.4.0
ARM GCC 9.5.0
ARM GCC trunk
ARM gcc 10.2.1 (none)
ARM gcc 10.3.1 (2021.07 none)
ARM gcc 10.3.1 (2021.10 none)
ARM gcc 11.2.1 (none)
ARM gcc 5.4.1 (none)
ARM gcc 7.2.1 (none)
ARM gcc 8.2 (WinCE)
ARM gcc 8.3.1 (none)
ARM gcc 9.2.1 (none)
ARM msvc v19.0 (ex-WINE)
ARM msvc v19.10 (ex-WINE)
ARM msvc v19.14 (ex-WINE)
ARM64 Morello gcc 10.1 Alpha 2
ARM64 gcc 10.2
ARM64 gcc 10.3
ARM64 gcc 10.4
ARM64 gcc 10.5.0
ARM64 gcc 11.1
ARM64 gcc 11.2
ARM64 gcc 11.3
ARM64 gcc 11.4.0
ARM64 gcc 12.1
ARM64 gcc 12.2.0
ARM64 gcc 12.3.0
ARM64 gcc 12.4.0
ARM64 gcc 12.5.0
ARM64 gcc 13.1.0
ARM64 gcc 13.2.0
ARM64 gcc 13.3.0
ARM64 gcc 13.4.0
ARM64 gcc 14.1.0
ARM64 gcc 14.2.0
ARM64 gcc 14.3.0
ARM64 gcc 15.1.0
ARM64 gcc 15.2.0
ARM64 gcc 4.9.4
ARM64 gcc 5.4
ARM64 gcc 5.5.0
ARM64 gcc 6.3
ARM64 gcc 6.4
ARM64 gcc 7.3
ARM64 gcc 7.5
ARM64 gcc 8.2
ARM64 gcc 8.5
ARM64 gcc 9.3
ARM64 gcc 9.4
ARM64 gcc 9.5
ARM64 gcc trunk
ARM64 msvc v19.14 (ex-WINE)
AVR gcc 10.3.0
AVR gcc 11.1.0
AVR gcc 12.1.0
AVR gcc 12.2.0
AVR gcc 12.3.0
AVR gcc 12.4.0
AVR gcc 12.5.0
AVR gcc 13.1.0
AVR gcc 13.2.0
AVR gcc 13.3.0
AVR gcc 13.4.0
AVR gcc 14.1.0
AVR gcc 14.2.0
AVR gcc 14.3.0
AVR gcc 15.1.0
AVR gcc 15.2.0
AVR gcc 4.5.4
AVR gcc 4.6.4
AVR gcc 5.4.0
AVR gcc 9.2.0
AVR gcc 9.3.0
Arduino Mega (1.8.9)
Arduino Uno (1.8.9)
BPF clang (trunk)
BPF clang 13.0.0
BPF clang 14.0.0
BPF clang 15.0.0
BPF clang 16.0.0
BPF clang 17.0.1
BPF clang 18.1.0
BPF clang 19.1.0
BPF clang 20.1.0
BPF clang 21.1.0
EDG (experimental reflection)
EDG 6.5
EDG 6.5 (GNU mode gcc 13)
EDG 6.6
EDG 6.6 (GNU mode gcc 13)
EDG 6.7
EDG 6.7 (GNU mode gcc 14)
FRC 2019
FRC 2020
FRC 2023
HPPA gcc 14.2.0
HPPA gcc 14.3.0
HPPA gcc 15.1.0
HPPA gcc 15.2.0
KVX ACB 4.1.0 (GCC 7.5.0)
KVX ACB 4.1.0-cd1 (GCC 7.5.0)
KVX ACB 4.10.0 (GCC 10.3.1)
KVX ACB 4.11.1 (GCC 10.3.1)
KVX ACB 4.12.0 (GCC 11.3.0)
KVX ACB 4.2.0 (GCC 7.5.0)
KVX ACB 4.3.0 (GCC 7.5.0)
KVX ACB 4.4.0 (GCC 7.5.0)
KVX ACB 4.6.0 (GCC 9.4.1)
KVX ACB 4.8.0 (GCC 9.4.1)
KVX ACB 4.9.0 (GCC 9.4.1)
KVX ACB 5.0.0 (GCC 12.2.1)
KVX ACB 5.2.0 (GCC 13.2.1)
LoongArch64 clang (trunk)
LoongArch64 clang 17.0.1
LoongArch64 clang 18.1.0
LoongArch64 clang 19.1.0
LoongArch64 clang 20.1.0
LoongArch64 clang 21.1.0
M68K gcc 13.1.0
M68K gcc 13.2.0
M68K gcc 13.3.0
M68K gcc 13.4.0
M68K gcc 14.1.0
M68K gcc 14.2.0
M68K gcc 14.3.0
M68K gcc 15.1.0
M68K gcc 15.2.0
M68k clang (trunk)
MRISC32 gcc (trunk)
MSP430 gcc 4.5.3
MSP430 gcc 5.3.0
MSP430 gcc 6.2.1
MinGW clang 14.0.3
MinGW clang 14.0.6
MinGW clang 15.0.7
MinGW clang 16.0.0
MinGW clang 16.0.2
MinGW gcc 11.3.0
MinGW gcc 12.1.0
MinGW gcc 12.2.0
MinGW gcc 13.1.0
MinGW gcc 14.3.0
MinGW gcc 15.2.0
RISC-V (32-bits) gcc (trunk)
RISC-V (32-bits) gcc 10.2.0
RISC-V (32-bits) gcc 10.3.0
RISC-V (32-bits) gcc 11.2.0
RISC-V (32-bits) gcc 11.3.0
RISC-V (32-bits) gcc 11.4.0
RISC-V (32-bits) gcc 12.1.0
RISC-V (32-bits) gcc 12.2.0
RISC-V (32-bits) gcc 12.3.0
RISC-V (32-bits) gcc 12.4.0
RISC-V (32-bits) gcc 12.5.0
RISC-V (32-bits) gcc 13.1.0
RISC-V (32-bits) gcc 13.2.0
RISC-V (32-bits) gcc 13.3.0
RISC-V (32-bits) gcc 13.4.0
RISC-V (32-bits) gcc 14.1.0
RISC-V (32-bits) gcc 14.2.0
RISC-V (32-bits) gcc 14.3.0
RISC-V (32-bits) gcc 15.1.0
RISC-V (32-bits) gcc 15.2.0
RISC-V (32-bits) gcc 8.2.0
RISC-V (32-bits) gcc 8.5.0
RISC-V (32-bits) gcc 9.4.0
RISC-V (64-bits) gcc (trunk)
RISC-V (64-bits) gcc 10.2.0
RISC-V (64-bits) gcc 10.3.0
RISC-V (64-bits) gcc 11.2.0
RISC-V (64-bits) gcc 11.3.0
RISC-V (64-bits) gcc 11.4.0
RISC-V (64-bits) gcc 12.1.0
RISC-V (64-bits) gcc 12.2.0
RISC-V (64-bits) gcc 12.3.0
RISC-V (64-bits) gcc 12.4.0
RISC-V (64-bits) gcc 12.5.0
RISC-V (64-bits) gcc 13.1.0
RISC-V (64-bits) gcc 13.2.0
RISC-V (64-bits) gcc 13.3.0
RISC-V (64-bits) gcc 13.4.0
RISC-V (64-bits) gcc 14.1.0
RISC-V (64-bits) gcc 14.2.0
RISC-V (64-bits) gcc 14.3.0
RISC-V (64-bits) gcc 15.1.0
RISC-V (64-bits) gcc 15.2.0
RISC-V (64-bits) gcc 8.2.0
RISC-V (64-bits) gcc 8.5.0
RISC-V (64-bits) gcc 9.4.0
RISC-V rv32gc clang (trunk)
RISC-V rv32gc clang 10.0.0
RISC-V rv32gc clang 10.0.1
RISC-V rv32gc clang 11.0.0
RISC-V rv32gc clang 11.0.1
RISC-V rv32gc clang 12.0.0
RISC-V rv32gc clang 12.0.1
RISC-V rv32gc clang 13.0.0
RISC-V rv32gc clang 13.0.1
RISC-V rv32gc clang 14.0.0
RISC-V rv32gc clang 15.0.0
RISC-V rv32gc clang 16.0.0
RISC-V rv32gc clang 17.0.1
RISC-V rv32gc clang 18.1.0
RISC-V rv32gc clang 19.1.0
RISC-V rv32gc clang 20.1.0
RISC-V rv32gc clang 21.1.0
RISC-V rv32gc clang 9.0.0
RISC-V rv32gc clang 9.0.1
RISC-V rv64gc clang (trunk)
RISC-V rv64gc clang 10.0.0
RISC-V rv64gc clang 10.0.1
RISC-V rv64gc clang 11.0.0
RISC-V rv64gc clang 11.0.1
RISC-V rv64gc clang 12.0.0
RISC-V rv64gc clang 12.0.1
RISC-V rv64gc clang 13.0.0
RISC-V rv64gc clang 13.0.1
RISC-V rv64gc clang 14.0.0
RISC-V rv64gc clang 15.0.0
RISC-V rv64gc clang 16.0.0
RISC-V rv64gc clang 17.0.1
RISC-V rv64gc clang 18.1.0
RISC-V rv64gc clang 19.1.0
RISC-V rv64gc clang 20.1.0
RISC-V rv64gc clang 21.1.0
RISC-V rv64gc clang 9.0.0
RISC-V rv64gc clang 9.0.1
Raspbian Buster
Raspbian Stretch
SPARC LEON gcc 12.2.0
SPARC LEON gcc 12.3.0
SPARC LEON gcc 12.4.0
SPARC LEON gcc 12.5.0
SPARC LEON gcc 13.1.0
SPARC LEON gcc 13.2.0
SPARC LEON gcc 13.3.0
SPARC LEON gcc 13.4.0
SPARC LEON gcc 14.1.0
SPARC LEON gcc 14.2.0
SPARC LEON gcc 14.3.0
SPARC LEON gcc 15.1.0
SPARC LEON gcc 15.2.0
SPARC gcc 12.2.0
SPARC gcc 12.3.0
SPARC gcc 12.4.0
SPARC gcc 12.5.0
SPARC gcc 13.1.0
SPARC gcc 13.2.0
SPARC gcc 13.3.0
SPARC gcc 13.4.0
SPARC gcc 14.1.0
SPARC gcc 14.2.0
SPARC gcc 14.3.0
SPARC gcc 15.1.0
SPARC gcc 15.2.0
SPARC64 gcc 12.2.0
SPARC64 gcc 12.3.0
SPARC64 gcc 12.4.0
SPARC64 gcc 12.5.0
SPARC64 gcc 13.1.0
SPARC64 gcc 13.2.0
SPARC64 gcc 13.3.0
SPARC64 gcc 13.4.0
SPARC64 gcc 14.1.0
SPARC64 gcc 14.2.0
SPARC64 gcc 14.3.0
SPARC64 gcc 15.1.0
SPARC64 gcc 15.2.0
TI C6x gcc 12.2.0
TI C6x gcc 12.3.0
TI C6x gcc 12.4.0
TI C6x gcc 12.5.0
TI C6x gcc 13.1.0
TI C6x gcc 13.2.0
TI C6x gcc 13.3.0
TI C6x gcc 13.4.0
TI C6x gcc 14.1.0
TI C6x gcc 14.2.0
TI C6x gcc 14.3.0
TI C6x gcc 15.1.0
TI C6x gcc 15.2.0
TI CL430 21.6.1
Tricore gcc 11.3.0 (EEESlab)
VAX gcc NetBSDELF 10.4.0
VAX gcc NetBSDELF 10.5.0 (Nov 15 03:50:22 2023)
VAX gcc NetBSDELF 12.4.0 (Apr 16 05:27 2025)
WebAssembly clang (trunk)
Xtensa ESP32 gcc 11.2.0 (2022r1)
Xtensa ESP32 gcc 12.2.0 (20230208)
Xtensa ESP32 gcc 14.2.0 (20241119)
Xtensa ESP32 gcc 8.2.0 (2019r2)
Xtensa ESP32 gcc 8.2.0 (2020r1)
Xtensa ESP32 gcc 8.2.0 (2020r2)
Xtensa ESP32 gcc 8.4.0 (2020r3)
Xtensa ESP32 gcc 8.4.0 (2021r1)
Xtensa ESP32 gcc 8.4.0 (2021r2)
Xtensa ESP32-S2 gcc 11.2.0 (2022r1)
Xtensa ESP32-S2 gcc 12.2.0 (20230208)
Xtensa ESP32-S2 gcc 14.2.0 (20241119)
Xtensa ESP32-S2 gcc 8.2.0 (2019r2)
Xtensa ESP32-S2 gcc 8.2.0 (2020r1)
Xtensa ESP32-S2 gcc 8.2.0 (2020r2)
Xtensa ESP32-S2 gcc 8.4.0 (2020r3)
Xtensa ESP32-S2 gcc 8.4.0 (2021r1)
Xtensa ESP32-S2 gcc 8.4.0 (2021r2)
Xtensa ESP32-S3 gcc 11.2.0 (2022r1)
Xtensa ESP32-S3 gcc 12.2.0 (20230208)
Xtensa ESP32-S3 gcc 14.2.0 (20241119)
Xtensa ESP32-S3 gcc 8.4.0 (2020r3)
Xtensa ESP32-S3 gcc 8.4.0 (2021r1)
Xtensa ESP32-S3 gcc 8.4.0 (2021r2)
arm64 msvc v19.20 VS16.0
arm64 msvc v19.21 VS16.1
arm64 msvc v19.22 VS16.2
arm64 msvc v19.23 VS16.3
arm64 msvc v19.24 VS16.4
arm64 msvc v19.25 VS16.5
arm64 msvc v19.27 VS16.7
arm64 msvc v19.28 VS16.8
arm64 msvc v19.28 VS16.9
arm64 msvc v19.29 VS16.10
arm64 msvc v19.29 VS16.11
arm64 msvc v19.30 VS17.0
arm64 msvc v19.31 VS17.1
arm64 msvc v19.32 VS17.2
arm64 msvc v19.33 VS17.3
arm64 msvc v19.34 VS17.4
arm64 msvc v19.35 VS17.5
arm64 msvc v19.36 VS17.6
arm64 msvc v19.37 VS17.7
arm64 msvc v19.38 VS17.8
arm64 msvc v19.39 VS17.9
arm64 msvc v19.40 VS17.10
arm64 msvc v19.41 VS17.11
arm64 msvc v19.42 VS17.12
arm64 msvc v19.43 VS17.13
arm64 msvc v19.44 VS17.14
arm64 msvc v19.latest
armv7-a clang (trunk)
armv7-a clang 10.0.0
armv7-a clang 10.0.1
armv7-a clang 11.0.0
armv7-a clang 11.0.1
armv7-a clang 12.0.0
armv7-a clang 12.0.1
armv7-a clang 13.0.0
armv7-a clang 13.0.1
armv7-a clang 14.0.0
armv7-a clang 15.0.0
armv7-a clang 16.0.0
armv7-a clang 17.0.1
armv7-a clang 18.1.0
armv7-a clang 19.1.0
armv7-a clang 20.1.0
armv7-a clang 21.1.0
armv7-a clang 9.0.0
armv7-a clang 9.0.1
armv8-a clang (all architectural features, trunk)
armv8-a clang (trunk)
armv8-a clang 10.0.0
armv8-a clang 10.0.1
armv8-a clang 11.0.0
armv8-a clang 11.0.1
armv8-a clang 12.0.0
armv8-a clang 13.0.0
armv8-a clang 14.0.0
armv8-a clang 15.0.0
armv8-a clang 16.0.0
armv8-a clang 17.0.1
armv8-a clang 18.1.0
armv8-a clang 19.1.0
armv8-a clang 20.1.0
armv8-a clang 21.1.0
armv8-a clang 9.0.0
armv8-a clang 9.0.1
clad trunk (clang 21.1.0)
clad v1.10 (clang 20.1.0)
clad v1.8 (clang 18.1.0)
clad v1.9 (clang 19.1.0)
clad v2.00 (clang 20.1.0)
clad v2.1 (clang 21.1.0)
clang-cl 18.1.0
ellcc 0.1.33
ellcc 0.1.34
ellcc 2017-07-16
ez80-clang 15.0.0
ez80-clang 15.0.7
hexagon-clang 16.0.5
llvm-mos atari2600-3e
llvm-mos atari2600-4k
llvm-mos atari2600-common
llvm-mos atari5200-supercart
llvm-mos atari8-cart-megacart
llvm-mos atari8-cart-std
llvm-mos atari8-cart-xegs
llvm-mos atari8-common
llvm-mos atari8-dos
llvm-mos c128
llvm-mos c64
llvm-mos commodore
llvm-mos cpm65
llvm-mos cx16
llvm-mos dodo
llvm-mos eater
llvm-mos mega65
llvm-mos nes
llvm-mos nes-action53
llvm-mos nes-cnrom
llvm-mos nes-gtrom
llvm-mos nes-mmc1
llvm-mos nes-mmc3
llvm-mos nes-nrom
llvm-mos nes-unrom
llvm-mos nes-unrom-512
llvm-mos osi-c1p
llvm-mos pce
llvm-mos pce-cd
llvm-mos pce-common
llvm-mos pet
llvm-mos rp6502
llvm-mos rpc8e
llvm-mos supervision
llvm-mos vic20
loongarch64 gcc 12.2.0
loongarch64 gcc 12.3.0
loongarch64 gcc 12.4.0
loongarch64 gcc 12.5.0
loongarch64 gcc 13.1.0
loongarch64 gcc 13.2.0
loongarch64 gcc 13.3.0
loongarch64 gcc 13.4.0
loongarch64 gcc 14.1.0
loongarch64 gcc 14.2.0
loongarch64 gcc 14.3.0
loongarch64 gcc 15.1.0
loongarch64 gcc 15.2.0
mips clang 13.0.0
mips clang 14.0.0
mips clang 15.0.0
mips clang 16.0.0
mips clang 17.0.1
mips clang 18.1.0
mips clang 19.1.0
mips clang 20.1.0
mips clang 21.1.0
mips gcc 11.2.0
mips gcc 12.1.0
mips gcc 12.2.0
mips gcc 12.3.0
mips gcc 12.4.0
mips gcc 12.5.0
mips gcc 13.1.0
mips gcc 13.2.0
mips gcc 13.3.0
mips gcc 13.4.0
mips gcc 14.1.0
mips gcc 14.2.0
mips gcc 14.3.0
mips gcc 15.1.0
mips gcc 15.2.0
mips gcc 4.9.4
mips gcc 5.4
mips gcc 5.5.0
mips gcc 9.3.0 (codescape)
mips gcc 9.5.0
mips64 (el) gcc 12.1.0
mips64 (el) gcc 12.2.0
mips64 (el) gcc 12.3.0
mips64 (el) gcc 12.4.0
mips64 (el) gcc 12.5.0
mips64 (el) gcc 13.1.0
mips64 (el) gcc 13.2.0
mips64 (el) gcc 13.3.0
mips64 (el) gcc 13.4.0
mips64 (el) gcc 14.1.0
mips64 (el) gcc 14.2.0
mips64 (el) gcc 14.3.0
mips64 (el) gcc 15.1.0
mips64 (el) gcc 15.2.0
mips64 (el) gcc 4.9.4
mips64 (el) gcc 5.4.0
mips64 (el) gcc 5.5.0
mips64 (el) gcc 9.5.0
mips64 clang 13.0.0
mips64 clang 14.0.0
mips64 clang 15.0.0
mips64 clang 16.0.0
mips64 clang 17.0.1
mips64 clang 18.1.0
mips64 clang 19.1.0
mips64 clang 20.1.0
mips64 clang 21.1.0
mips64 gcc 11.2.0
mips64 gcc 12.1.0
mips64 gcc 12.2.0
mips64 gcc 12.3.0
mips64 gcc 12.4.0
mips64 gcc 12.5.0
mips64 gcc 13.1.0
mips64 gcc 13.2.0
mips64 gcc 13.3.0
mips64 gcc 13.4.0
mips64 gcc 14.1.0
mips64 gcc 14.2.0
mips64 gcc 14.3.0
mips64 gcc 15.1.0
mips64 gcc 15.2.0
mips64 gcc 4.9.4
mips64 gcc 5.4.0
mips64 gcc 5.5.0
mips64 gcc 9.5.0
mips64el clang 13.0.0
mips64el clang 14.0.0
mips64el clang 15.0.0
mips64el clang 16.0.0
mips64el clang 17.0.1
mips64el clang 18.1.0
mips64el clang 19.1.0
mips64el clang 20.1.0
mips64el clang 21.1.0
mipsel clang 13.0.0
mipsel clang 14.0.0
mipsel clang 15.0.0
mipsel clang 16.0.0
mipsel clang 17.0.1
mipsel clang 18.1.0
mipsel clang 19.1.0
mipsel clang 20.1.0
mipsel clang 21.1.0
mipsel gcc 12.1.0
mipsel gcc 12.2.0
mipsel gcc 12.3.0
mipsel gcc 12.4.0
mipsel gcc 12.5.0
mipsel gcc 13.1.0
mipsel gcc 13.2.0
mipsel gcc 13.3.0
mipsel gcc 13.4.0
mipsel gcc 14.1.0
mipsel gcc 14.2.0
mipsel gcc 14.3.0
mipsel gcc 15.1.0
mipsel gcc 15.2.0
mipsel gcc 4.9.4
mipsel gcc 5.4.0
mipsel gcc 5.5.0
mipsel gcc 9.5.0
nanoMIPS gcc 6.3.0 (mtk)
power gcc 11.2.0
power gcc 12.1.0
power gcc 12.2.0
power gcc 12.3.0
power gcc 12.4.0
power gcc 12.5.0
power gcc 13.1.0
power gcc 13.2.0
power gcc 13.3.0
power gcc 13.4.0
power gcc 14.1.0
power gcc 14.2.0
power gcc 14.3.0
power gcc 15.1.0
power gcc 15.2.0
power gcc 4.8.5
power64 AT12.0 (gcc8)
power64 AT13.0 (gcc9)
power64 gcc 11.2.0
power64 gcc 12.1.0
power64 gcc 12.2.0
power64 gcc 12.3.0
power64 gcc 12.4.0
power64 gcc 12.5.0
power64 gcc 13.1.0
power64 gcc 13.2.0
power64 gcc 13.3.0
power64 gcc 13.4.0
power64 gcc 14.1.0
power64 gcc 14.2.0
power64 gcc 14.3.0
power64 gcc 15.1.0
power64 gcc 15.2.0
power64 gcc trunk
power64le AT12.0 (gcc8)
power64le AT13.0 (gcc9)
power64le clang (trunk)
power64le gcc 11.2.0
power64le gcc 12.1.0
power64le gcc 12.2.0
power64le gcc 12.3.0
power64le gcc 12.4.0
power64le gcc 12.5.0
power64le gcc 13.1.0
power64le gcc 13.2.0
power64le gcc 13.3.0
power64le gcc 13.4.0
power64le gcc 14.1.0
power64le gcc 14.2.0
power64le gcc 14.3.0
power64le gcc 15.1.0
power64le gcc 15.2.0
power64le gcc 6.3.0
power64le gcc trunk
powerpc64 clang (trunk)
qnx 8.0.0
s390x gcc 11.2.0
s390x gcc 12.1.0
s390x gcc 12.2.0
s390x gcc 12.3.0
s390x gcc 12.4.0
s390x gcc 12.5.0
s390x gcc 13.1.0
s390x gcc 13.2.0
s390x gcc 13.3.0
s390x gcc 13.4.0
s390x gcc 14.1.0
s390x gcc 14.2.0
s390x gcc 14.3.0
s390x gcc 15.1.0
s390x gcc 15.2.0
sh gcc 12.2.0
sh gcc 12.3.0
sh gcc 12.4.0
sh gcc 12.5.0
sh gcc 13.1.0
sh gcc 13.2.0
sh gcc 13.3.0
sh gcc 13.4.0
sh gcc 14.1.0
sh gcc 14.2.0
sh gcc 14.3.0
sh gcc 15.1.0
sh gcc 15.2.0
sh gcc 4.9.4
sh gcc 9.5.0
vast (trunk)
x64 msvc v19.0 (ex-WINE)
x64 msvc v19.10 (ex-WINE)
x64 msvc v19.14 (ex-WINE)
x64 msvc v19.20 VS16.0
x64 msvc v19.21 VS16.1
x64 msvc v19.22 VS16.2
x64 msvc v19.23 VS16.3
x64 msvc v19.24 VS16.4
x64 msvc v19.25 VS16.5
x64 msvc v19.27 VS16.7
x64 msvc v19.28 VS16.8
x64 msvc v19.28 VS16.9
x64 msvc v19.29 VS16.10
x64 msvc v19.29 VS16.11
x64 msvc v19.30 VS17.0
x64 msvc v19.31 VS17.1
x64 msvc v19.32 VS17.2
x64 msvc v19.33 VS17.3
x64 msvc v19.34 VS17.4
x64 msvc v19.35 VS17.5
x64 msvc v19.36 VS17.6
x64 msvc v19.37 VS17.7
x64 msvc v19.38 VS17.8
x64 msvc v19.39 VS17.9
x64 msvc v19.40 VS17.10
x64 msvc v19.41 VS17.11
x64 msvc v19.42 VS17.12
x64 msvc v19.43 VS17.13
x64 msvc v19.44 VS17.14
x64 msvc v19.latest
x86 djgpp 4.9.4
x86 djgpp 5.5.0
x86 djgpp 6.4.0
x86 djgpp 7.2.0
x86 g++ 1.27
x86 msvc v19.0 (ex-WINE)
x86 msvc v19.10 (ex-WINE)
x86 msvc v19.14 (ex-WINE)
x86 msvc v19.20 VS16.0
x86 msvc v19.21 VS16.1
x86 msvc v19.22 VS16.2
x86 msvc v19.23 VS16.3
x86 msvc v19.24 VS16.4
x86 msvc v19.25 VS16.5
x86 msvc v19.27 VS16.7
x86 msvc v19.28 VS16.8
x86 msvc v19.28 VS16.9
x86 msvc v19.29 VS16.10
x86 msvc v19.29 VS16.11
x86 msvc v19.30 VS17.0
x86 msvc v19.31 VS17.1
x86 msvc v19.32 VS17.2
x86 msvc v19.33 VS17.3
x86 msvc v19.34 VS17.4
x86 msvc v19.35 VS17.5
x86 msvc v19.36 VS17.6
x86 msvc v19.37 VS17.7
x86 msvc v19.38 VS17.8
x86 msvc v19.39 VS17.9
x86 msvc v19.40 VS17.10
x86 msvc v19.41 VS17.11
x86 msvc v19.42 VS17.12
x86 msvc v19.43 VS17.13
x86 msvc v19.44 VS17.14
x86 msvc v19.latest
x86 nvc++ 22.11
x86 nvc++ 22.7
x86 nvc++ 22.9
x86 nvc++ 23.1
x86 nvc++ 23.11
x86 nvc++ 23.3
x86 nvc++ 23.5
x86 nvc++ 23.7
x86 nvc++ 23.9
x86 nvc++ 24.1
x86 nvc++ 24.11
x86 nvc++ 24.3
x86 nvc++ 24.5
x86 nvc++ 24.7
x86 nvc++ 24.9
x86 nvc++ 25.1
x86 nvc++ 25.3
x86 nvc++ 25.5
x86 nvc++ 25.7
x86 nvc++ 25.9
x86-64 Zapcc 190308
x86-64 clang (-fimplicit-constexpr)
x86-64 clang (Chris Bazley N3089)
x86-64 clang (EricWF contracts)
x86-64 clang (amd-staging)
x86-64 clang (assertions trunk)
x86-64 clang (clangir)
x86-64 clang (experimental -Wlifetime)
x86-64 clang (experimental P1061)
x86-64 clang (experimental P1144)
x86-64 clang (experimental P1221)
x86-64 clang (experimental P2561)
x86-64 clang (experimental P2998)
x86-64 clang (experimental P3068)
x86-64 clang (experimental P3309)
x86-64 clang (experimental P3334)
x86-64 clang (experimental P3367)
x86-64 clang (experimental P3372)
x86-64 clang (experimental P3385)
x86-64 clang (experimental P3776)
x86-64 clang (experimental metaprogramming - P2632)
x86-64 clang (old concepts branch)
x86-64 clang (p1974)
x86-64 clang (pattern matching - P2688)
x86-64 clang (reflection - C++26)
x86-64 clang (reflection - TS)
x86-64 clang (resugar)
x86-64 clang (string interpolation - P3412)
x86-64 clang (thephd.dev)
x86-64 clang (trunk)
x86-64 clang (variadic friends - P2893)
x86-64 clang (widberg)
x86-64 clang 10.0.0
x86-64 clang 10.0.0 (assertions)
x86-64 clang 10.0.1
x86-64 clang 11.0.0
x86-64 clang 11.0.0 (assertions)
x86-64 clang 11.0.1
x86-64 clang 12.0.0
x86-64 clang 12.0.0 (assertions)
x86-64 clang 12.0.1
x86-64 clang 13.0.0
x86-64 clang 13.0.0 (assertions)
x86-64 clang 13.0.1
x86-64 clang 14.0.0
x86-64 clang 14.0.0 (assertions)
x86-64 clang 15.0.0
x86-64 clang 15.0.0 (assertions)
x86-64 clang 16.0.0
x86-64 clang 16.0.0 (assertions)
x86-64 clang 17.0.1
x86-64 clang 17.0.1 (assertions)
x86-64 clang 18.1.0
x86-64 clang 18.1.0 (assertions)
x86-64 clang 19.1.0
x86-64 clang 19.1.0 (assertions)
x86-64 clang 2.6.0 (assertions)
x86-64 clang 2.7.0 (assertions)
x86-64 clang 2.8.0 (assertions)
x86-64 clang 2.9.0 (assertions)
x86-64 clang 20.1.0
x86-64 clang 20.1.0 (assertions)
x86-64 clang 21.1.0
x86-64 clang 21.1.0 (assertions)
x86-64 clang 3.0.0
x86-64 clang 3.0.0 (assertions)
x86-64 clang 3.1
x86-64 clang 3.1 (assertions)
x86-64 clang 3.2
x86-64 clang 3.2 (assertions)
x86-64 clang 3.3
x86-64 clang 3.3 (assertions)
x86-64 clang 3.4 (assertions)
x86-64 clang 3.4.1
x86-64 clang 3.5
x86-64 clang 3.5 (assertions)
x86-64 clang 3.5.1
x86-64 clang 3.5.2
x86-64 clang 3.6
x86-64 clang 3.6 (assertions)
x86-64 clang 3.7
x86-64 clang 3.7 (assertions)
x86-64 clang 3.7.1
x86-64 clang 3.8
x86-64 clang 3.8 (assertions)
x86-64 clang 3.8.1
x86-64 clang 3.9.0
x86-64 clang 3.9.0 (assertions)
x86-64 clang 3.9.1
x86-64 clang 4.0.0
x86-64 clang 4.0.0 (assertions)
x86-64 clang 4.0.1
x86-64 clang 5.0.0
x86-64 clang 5.0.0 (assertions)
x86-64 clang 5.0.1
x86-64 clang 5.0.2
x86-64 clang 6.0.0
x86-64 clang 6.0.0 (assertions)
x86-64 clang 6.0.1
x86-64 clang 7.0.0
x86-64 clang 7.0.0 (assertions)
x86-64 clang 7.0.1
x86-64 clang 7.1.0
x86-64 clang 8.0.0
x86-64 clang 8.0.0 (assertions)
x86-64 clang 8.0.1
x86-64 clang 9.0.0
x86-64 clang 9.0.0 (assertions)
x86-64 clang 9.0.1
x86-64 clang rocm-4.5.2
x86-64 clang rocm-5.0.2
x86-64 clang rocm-5.1.3
x86-64 clang rocm-5.2.3
x86-64 clang rocm-5.3.3
x86-64 clang rocm-5.7.0
x86-64 clang rocm-6.0.2
x86-64 clang rocm-6.1.2
x86-64 clang rocm-6.2.4
x86-64 clang rocm-6.3.3
x86-64 clang rocm-6.4.0
x86-64 clang rocm-7.0.1
x86-64 gcc (C++26 contracts + GNU extensions)
x86-64 gcc (C++26 contracts)
x86-64 gcc (C++26 reflection)
x86-64 gcc (P2034 lambdas)
x86-64 gcc (Thomas Healy)
x86-64 gcc (contract labels)
x86-64 gcc (contracts natural syntax)
x86-64 gcc (contracts)
x86-64 gcc (coroutines)
x86-64 gcc (modules)
x86-64 gcc (trunk)
x86-64 gcc 10.1
x86-64 gcc 10.2
x86-64 gcc 10.3
x86-64 gcc 10.3 (assertions)
x86-64 gcc 10.4
x86-64 gcc 10.4 (assertions)
x86-64 gcc 10.5
x86-64 gcc 10.5 (assertions)
x86-64 gcc 11.1
x86-64 gcc 11.1 (assertions)
x86-64 gcc 11.2
x86-64 gcc 11.2 (assertions)
x86-64 gcc 11.3
x86-64 gcc 11.3 (assertions)
x86-64 gcc 11.4
x86-64 gcc 11.4 (assertions)
x86-64 gcc 12.1
x86-64 gcc 12.1 (assertions)
x86-64 gcc 12.2
x86-64 gcc 12.2 (assertions)
x86-64 gcc 12.3
x86-64 gcc 12.3 (assertions)
x86-64 gcc 12.4
x86-64 gcc 12.4 (assertions)
x86-64 gcc 12.5
x86-64 gcc 12.5 (assertions)
x86-64 gcc 13.1
x86-64 gcc 13.1 (assertions)
x86-64 gcc 13.2
x86-64 gcc 13.2 (assertions)
x86-64 gcc 13.3
x86-64 gcc 13.3 (assertions)
x86-64 gcc 13.4
x86-64 gcc 13.4 (assertions)
x86-64 gcc 14.1
x86-64 gcc 14.1 (assertions)
x86-64 gcc 14.2
x86-64 gcc 14.2 (assertions)
x86-64 gcc 14.3
x86-64 gcc 14.3 (assertions)
x86-64 gcc 15.1
x86-64 gcc 15.1 (assertions)
x86-64 gcc 15.2
x86-64 gcc 15.2 (assertions)
x86-64 gcc 3.4.6
x86-64 gcc 4.0.4
x86-64 gcc 4.1.2
x86-64 gcc 4.4.7
x86-64 gcc 4.5.3
x86-64 gcc 4.6.4
x86-64 gcc 4.7.1
x86-64 gcc 4.7.2
x86-64 gcc 4.7.3
x86-64 gcc 4.7.4
x86-64 gcc 4.8.1
x86-64 gcc 4.8.2
x86-64 gcc 4.8.3
x86-64 gcc 4.8.4
x86-64 gcc 4.8.5
x86-64 gcc 4.9.0
x86-64 gcc 4.9.1
x86-64 gcc 4.9.2
x86-64 gcc 4.9.3
x86-64 gcc 4.9.4
x86-64 gcc 5.1
x86-64 gcc 5.2
x86-64 gcc 5.3
x86-64 gcc 5.4
x86-64 gcc 5.5
x86-64 gcc 6.1
x86-64 gcc 6.2
x86-64 gcc 6.3
x86-64 gcc 6.4
x86-64 gcc 6.5
x86-64 gcc 7.1
x86-64 gcc 7.2
x86-64 gcc 7.3
x86-64 gcc 7.4
x86-64 gcc 7.5
x86-64 gcc 8.1
x86-64 gcc 8.2
x86-64 gcc 8.3
x86-64 gcc 8.4
x86-64 gcc 8.5
x86-64 gcc 9.1
x86-64 gcc 9.2
x86-64 gcc 9.3
x86-64 gcc 9.4
x86-64 gcc 9.5
x86-64 icc 13.0.1
x86-64 icc 16.0.3
x86-64 icc 17.0.0
x86-64 icc 18.0.0
x86-64 icc 19.0.0
x86-64 icc 19.0.1
x86-64 icc 2021.1.2
x86-64 icc 2021.10.0
x86-64 icc 2021.2.0
x86-64 icc 2021.3.0
x86-64 icc 2021.4.0
x86-64 icc 2021.5.0
x86-64 icc 2021.6.0
x86-64 icc 2021.7.0
x86-64 icc 2021.7.1
x86-64 icc 2021.8.0
x86-64 icc 2021.9.0
x86-64 icx 2021.1.2
x86-64 icx 2021.2.0
x86-64 icx 2021.3.0
x86-64 icx 2021.4.0
x86-64 icx 2022.0.0
x86-64 icx 2022.1.0
x86-64 icx 2022.2.0
x86-64 icx 2022.2.1
x86-64 icx 2023.0.0
x86-64 icx 2023.1.0
x86-64 icx 2023.2.1
x86-64 icx 2024.0.0
x86-64 icx 2024.1.0
x86-64 icx 2024.2.0
x86-64 icx 2024.2.1
x86-64 icx 2025.0.0
x86-64 icx 2025.0.1
x86-64 icx 2025.0.3
x86-64 icx 2025.0.4
x86-64 icx 2025.1.0
x86-64 icx 2025.1.1
x86-64 icx 2025.2.0
x86-64 icx 2025.2.1
x86-64 icx 2025.2.1
z180-clang 15.0.0
z180-clang 15.0.7
z80-clang 15.0.0
z80-clang 15.0.7
zig c++ 0.10.0
zig c++ 0.11.0
zig c++ 0.12.0
zig c++ 0.12.1
zig c++ 0.13.0
zig c++ 0.14.0
zig c++ 0.14.1
zig c++ 0.15.1
zig c++ 0.15.2
zig c++ 0.6.0
zig c++ 0.7.0
zig c++ 0.7.1
zig c++ 0.8.0
zig c++ 0.9.0
zig c++ trunk
Options
Source code
#include <fmt/core.h> // ApprovalTests.cpp version v.10.12.2 // More information at: https://github.com/approvals/ApprovalTests.cpp // // Copyright (c) 2022 Llewellyn Falco and Clare Macrae. All rights reserved. // // Distributed under the Apache 2.0 License // See https://opensource.org/licenses/Apache-2.0 //---------------------------------------------------------------------- // Welcome to Approval Tests. // // If you experience linker errors about missing symbols, it means // you have forgotten to configure your test framework for Approval Tests. // // For help with this, please see: // https://github.com/approvals/ApprovalTests.cpp/blob/master/doc/TroubleshootingMisconfiguredMain.md //---------------------------------------------------------------------- // ******************** From: ApprovalTests.hpp #ifndef APPROVAL_TESTS_CPP_APPROVALS_HPP #define APPROVAL_TESTS_CPP_APPROVALS_HPP // This file is machine-generated. Do not edit. // ******************** From: ApprovalTestsVersion.h #define APPROVAL_TESTS_VERSION_MAJOR 10 #define APPROVAL_TESTS_VERSION_MINOR 12 #define APPROVAL_TESTS_VERSION_PATCH 2 #define APPROVAL_TESTS_VERSION_STR "10.12.2" #define APPROVAL_TESTS_VERSION \ (APPROVAL_TESTS_VERSION_MAJOR * 10000 + APPROVAL_TESTS_VERSION_MINOR * 100 + \ APPROVAL_TESTS_VERSION_PATCH) // ******************** From: Reporter.h #include <string> namespace ApprovalTests { class Reporter { public: virtual ~Reporter() = default; virtual bool report(std::string received, std::string approved) const = 0; }; } // ******************** From: ReporterFactory.h #include <functional> #include <map> #include <memory> #include <string> #include <vector> namespace ApprovalTests { class ReporterFactory { public: using Reporters = std::map<std::string, std::function<std::unique_ptr<Reporter>()>>; ReporterFactory(); std::vector<std::string> allSupportedReporterNames() const; std::unique_ptr<Reporter> createReporter(const std::string& reporterName) const; std::string findReporterName(const std::string& osPrefix, const std::string& reporterName) const; private: static Reporters createMap(); Reporters map; }; } // ******************** From: DiffInfo.h #include <string> #include <utility> #include <vector> namespace ApprovalTests { enum class Type { TEXT, IMAGE, TEXT_AND_IMAGE }; struct DiffInfo { static std::string receivedFileTemplate(); static std::string approvedFileTemplate(); static std::string programFileTemplate(); static std::string getDefaultArguments(); DiffInfo(std::string program_, Type type_); DiffInfo(std::string program_, std::string arguments_, Type type_); std::string program; std::string arguments; Type type; static std::vector<std::string> getProgramFileLocations(); std::string getProgramForOs() const; }; } // ******************** From: DiffPrograms.h namespace ApprovalTests { namespace DiffPrograms { namespace Mac { DiffInfo DIFF_MERGE(); DiffInfo ARAXIS_MERGE(); DiffInfo BEYOND_COMPARE(); DiffInfo KALEIDOSCOPE(); DiffInfo SUBLIME_MERGE(); DiffInfo KDIFF3(); DiffInfo P4MERGE(); DiffInfo TK_DIFF(); DiffInfo VS_CODE(); DiffInfo CLION(); } namespace Linux { DiffInfo SUBLIME_MERGE_SNAP(); DiffInfo SUBLIME_MERGE_FLATPAK(); DiffInfo SUBLIME_MERGE_REPOSITORY_PACKAGE(); DiffInfo SUBLIME_MERGE_DIRECT_DOWNLOAD(); // More ideas available from: https://www.tecmint.com/best-linux-file-diff-tools-comparison/ DiffInfo KDIFF3(); DiffInfo MELD(); DiffInfo BEYOND_COMPARE(); } namespace Windows { DiffInfo BEYOND_COMPARE_3(); DiffInfo BEYOND_COMPARE_4(); DiffInfo TORTOISE_IMAGE_DIFF(); DiffInfo TORTOISE_TEXT_DIFF(); DiffInfo TORTOISE_GIT_IMAGE_DIFF(); DiffInfo TORTOISE_GIT_TEXT_DIFF(); DiffInfo WIN_MERGE_REPORTER(); DiffInfo ARAXIS_MERGE(); DiffInfo CODE_COMPARE(); DiffInfo SUBLIME_MERGE(); DiffInfo KDIFF3(); DiffInfo VS_CODE(); } } } // ******************** From: ConvertForCygwin.h #include <string> namespace ApprovalTests { class ConvertForCygwin { public: virtual ~ConvertForCygwin() = default; virtual std::string convertProgramForCygwin(const std::string& filePath); virtual std::string convertFileArgumentForCygwin(const std::string& filePath); }; class DoNothing : public ConvertForCygwin { public: std::string convertProgramForCygwin(const std::string& filePath) override; std::string convertFileArgumentForCygwin(const std::string& filePath) override; }; } // ******************** From: CommandLauncher.h #include <string> namespace ApprovalTests { // An interface to trigger execution of a command. See also SystemLauncher class CommandLauncher { public: virtual ~CommandLauncher() = default; virtual bool launch(const std::string& commandLine) = 0; virtual std::string getCommandLine(const std::string& commandLine) const = 0; }; } // ******************** From: CommandReporter.h #include <utility> #include <memory> namespace ApprovalTests { // Generic reporter to launch arbitrary command class CommandReporter : public Reporter { private: std::string cmd; std::string arguments = DiffInfo::getDefaultArguments(); CommandLauncher* l; std::shared_ptr<ConvertForCygwin> converter; std::string assembleFullCommand(const std::string& received, const std::string& approved) const; protected: CommandReporter(std::string command, CommandLauncher* launcher); CommandReporter(std::string command, std::string args, CommandLauncher* launcher); public: static bool exists(const std::string& command); bool report(std::string received, std::string approved) const override; std::string getCommandLine(const std::string& received, const std::string& approved) const; public: void checkForCygwin(); // Seam for testing void useCygwinConversions(bool useCygwin); }; } // ******************** From: ApprovalsMacroDefaults.h // This file intentionally left blank. // ******************** From: Macros.h // Use this in places where we have parameters that are sometimes unused, // e.g. because of #if // See https://stackoverflow.com/a/1486931/104370 #define APPROVAL_TESTS_UNUSED(expr) \ do \ { \ (void)(expr); \ } while (0) #if __cplusplus >= 201703L #define APPROVAL_TESTS_NO_DISCARD [[nodiscard]] #else #define APPROVAL_TESTS_NO_DISCARD #endif #if (__cplusplus >= 201402L) #define APPROVAL_TESTS_DEPRECATED(text) [[deprecated(text)]] #define APPROVAL_TESTS_DEPRECATED_CPP11(text) #else #define APPROVAL_TESTS_DEPRECATED(text) #define APPROVAL_TESTS_DEPRECATED_CPP11(text) \ MoreHelpMessages::deprecatedFunctionCalled(text, __FILE__, __LINE__); #endif #define APPROVAL_TESTS_REGISTER_MAIN_DIRECTORY \ auto approval_tests_registered_main_directory = \ ApprovalTests::TestName::registerRootDirectoryFromMainFile(__FILE__); // ******************** From: EmptyFileCreatorFactory.h #include <functional> #include <string> namespace ApprovalTests { using EmptyFileCreator = std::function<void(std::string)>; class EmptyFileCreatorFactory { public: static void defaultCreator(std::string fullFilePath); static EmptyFileCreator currentCreator; }; } // ******************** From: EmptyFileCreatorDisposer.h namespace ApprovalTests { class APPROVAL_TESTS_NO_DISCARD EmptyFileCreatorDisposer { private: EmptyFileCreator previous_result; public: explicit EmptyFileCreatorDisposer(EmptyFileCreator creator); EmptyFileCreatorDisposer(const EmptyFileCreatorDisposer&) = default; ~EmptyFileCreatorDisposer(); }; } // ******************** From: FileUtils.h #include <string> namespace ApprovalTests { class FileUtils { public: static bool fileExists(const std::string& path); static int fileSize(const std::string& path); static EmptyFileCreatorDisposer useEmptyFileCreator(EmptyFileCreator creator); static void ensureFileExists(const std::string& fullFilePath); static std::string getDirectory(const std::string& filePath); static std::string getExtensionWithDot(const std::string& filePath); static std::string readFileThrowIfMissing(const std::string& fileName); static std::string readFileReturnEmptyIfMissing(const std::string& fileName); static void writeToFile(const std::string& filePath, const std::string& content); }; } // ******************** From: WinMinGWUtils.h #if (defined(__MINGW32__) || defined(__MINGW64__)) #define APPROVAL_TESTS_MINGW #endif #ifdef APPROVAL_TESTS_MINGW #ifdef __cplusplus extern "C" { #endif #include <sec_api/stdlib_s.h> /* errno_t, size_t */ errno_t getenv_s(size_t* ret_required_buf_size, char* buf, size_t buf_size_in_bytes, const char* name); #ifdef __cplusplus } #endif #endif // APPROVAL_TESTS_MINGW // ******************** From: StringMaker.h #include <string> #include <sstream> namespace ApprovalTests { class StringMaker { public: template <typename T> static std::string toString(const T& contents) { std::stringstream s; s << contents; return s.str(); } }; } // ******************** From: StringUtils.h #include <string> #include <algorithm> #include <sstream> namespace ApprovalTests { class StringUtils { public: static std::string replaceAll(std::string inText, const std::string& find, const std::string& replaceWith); static bool contains(const std::string& inText, const std::string& find); static std::string toLower(std::string inText); static bool beginsWith(std::string value, std::string beginning); static bool endsWith(std::string value, std::string ending); template <typename T> static std::string toString(const T& contents) { return StringMaker::toString(contents); } static std::string leftTrim(std::string s); static std::string rightTrim(std::string s); static std::string trim(std::string s); }; } // ******************** From: SystemUtils.h #ifdef _WIN32 // ReSharper disable once CppUnusedIncludeDirective #include <io.h> #include <direct.h> #else // ReSharper disable once CppUnusedIncludeDirective #include <unistd.h> #endif #include <stdexcept> namespace ApprovalTests { class SystemUtils { public: static bool isWindowsOs(); static bool isCygwin(); static bool isMacOs(); static std::string getDirectorySeparator(); // Properly cases the filename, but not the directories, on Windows. static std::string checkFilenameCase(const std::string& fullPath); static std::string safeGetEnvForWindows(char const* name); static std::string safeGetEnvForNonWindows(char const* name); //! Return the value of the environment variable, or an empty string if the variable is not set. static std::string safeGetEnv(char const* name); static std::string getMachineName(); static void makeDirectoryForWindows(const std::string& directory); static void makeDirectoryForNonWindows(const std::string& directory); static void makeDirectory(const std::string& directory); static void ensureDirectoryExists(const std::string& fullDirectoryPath); static void ensureParentDirectoryExists(const std::string& fullFilePath); static void runSystemCommandOrThrow(const std::string& command, bool allowNonZeroExitCodes = false); }; } // ******************** From: SystemLauncher.h #include <string> namespace ApprovalTests { class SystemLauncher : public CommandLauncher { private: bool useWindows_ = SystemUtils::isWindowsOs(); bool isForeground_ = false; bool allowNonZeroExitCodes_ = false; public: explicit SystemLauncher(bool isForeground = false, bool allowNonZeroExitCodes = false); bool launch(const std::string& commandLine) override; // Seam for testing void invokeForWindows(bool useWindows); void setForeground(bool foreground); void setAllowNonZeroExitCodes(bool allow); bool isForeground() const; std::string getCommandLine(const std::string& commandLine) const override; std::string getWindowsCommandLine(const std::string& commandLine, bool foreground) const; std::string getUnixCommandLine(const std::string& commandLine, bool foreground) const; }; } // ******************** From: GenericDiffReporter.h #include <string> namespace ApprovalTests { class GenericDiffReporter : public CommandReporter { public: SystemLauncher launcher; public: explicit GenericDiffReporter(const std::string& program); explicit GenericDiffReporter(const DiffInfo& info); }; } // ******************** From: QuietReporter.h namespace ApprovalTests { // A reporter that does nothing. Failing tests will still fail, but nothing will be launched. class QuietReporter : public Reporter { public: bool report(std::string /*received*/, std::string /*approved*/) const override; }; } // ******************** From: TextDiffReporter.h #include <memory> #include <iosfwd> namespace ApprovalTests { // A Reporter class that only uses text-based diff tools, with output written // to the console. It provides no opportunity for interactive approving // of files. // It currently has a short, hard-coded list of diffing tools. class TextDiffReporter : public Reporter { private: std::unique_ptr<Reporter> m_reporter; std::ostream& stream_; public: TextDiffReporter(); explicit TextDiffReporter(std::ostream& stream); bool report(std::string received, std::string approved) const override; }; } // namespace ApprovalTests // ******************** From: Blocker.h namespace ApprovalTests { class Blocker { public: Blocker() = default; Blocker(const Blocker&) = default; Blocker(Blocker&&) = default; Blocker& operator=(const Blocker&) = default; Blocker& operator=(Blocker&&) = default; virtual ~Blocker() = default; virtual bool isBlockingOnThisMachine() const = 0; }; } // ******************** From: MachineBlocker.h #include <utility> namespace ApprovalTests { class MachineBlocker : public Blocker { private: std::string machineName; bool block; MachineBlocker() = delete; public: MachineBlocker(std::string machineName_, bool block_); static MachineBlocker onMachineNamed(const std::string& machineName); static MachineBlocker onMachinesNotNamed(const std::string& machineName); virtual bool isBlockingOnThisMachine() const override; }; } // ******************** From: AutoApproveReporter.h namespace ApprovalTests { class AutoApproveReporter : public Reporter { public: bool report(std::string received, std::string approved) const override; }; } // ******************** From: Path.h #include <string> namespace ApprovalTests { class Path { std::string path_; std::string separator_ = SystemUtils::getDirectorySeparator(); public: Path(const std::string& start); std::string toString() const; std::string toString(const std::string& directoryPathSeparator) const; Path operator+(const std::string& addition) const; Path operator/(const std::string& addition) const; Path operator/(const Path addition) const; static std::string normalizeSeparators(const std::string& path); std::string removeRedundantDirectorySeparators(std::string path) const; }; } // ******************** From: ApprovalNamer.h #include <string> namespace ApprovalTests { class ApprovalNamer { public: virtual ~ApprovalNamer() = default; virtual std::string getApprovedFile(std::string extensionWithDot) const = 0; virtual std::string getReceivedFile(std::string extensionWithDot) const = 0; }; } // ******************** From: ApprovalTestNamer.h #include <vector> #include <stdexcept> namespace ApprovalTests { class TestName { public: const std::string& getFileName() const; std::string getOriginalFileName(); void setFileName(const std::string& file); private: static void checkBuildConfiguration(const std::string& fileName); public: static std::string getMisconfiguredBuildHelp(const std::string& fileName); static std::string checkParentDirectoriesForFile(const std::string& file); static bool registerRootDirectoryFromMainFile(const std::string& file); static std::string getRootDirectory(); std::vector<std::string> sections; static std::string directoryPrefix; static bool checkBuildConfig_; private: std::string handleBoostQuirks() const; std::string findFileName(const std::string& file); static std::string& rootDirectoryStorage(); std::string fileName; std::string originalFileName; }; class TestConfiguration { public: std::string subdirectory; }; class ApprovalTestNamer : public ApprovalNamer { private: public: ApprovalTestNamer() = default; std::string getTestName() const; static std::string convertToFileName(const std::string& fileName); static TestName& getCurrentTest(); static std::string getMisconfiguredMainHelp(); // Deprecated - please use getSourceFileName std::string getFileName() const; std::string getSourceFileName() const; std::string getTestSourceDirectory() const; std::string getRelativeTestSourceDirectory() const; std::string getApprovalsSubdirectory() const; std::string getDirectory() const; static TestName& currentTest(TestName* value = nullptr); static TestConfiguration& testConfiguration(); virtual std::string getApprovedFile(std::string extensionWithDot) const override; virtual std::string getReceivedFile(std::string extensionWithDot) const override; std::string getOutputFileBaseName() const; std::string getFullFileName(const std::string& approved, const std::string& extensionWithDot) const; static bool setCheckBuildConfig(bool enabled); }; } // ******************** From: SectionNameDisposer.h namespace ApprovalTests { class APPROVAL_TESTS_NO_DISCARD SectionNameDisposer { public: SectionNameDisposer(TestName& currentTest_, const std::string& scope_name); SectionNameDisposer(const SectionNameDisposer&) = default; ~SectionNameDisposer(); private: TestName& currentTest; }; } // ******************** From: NamerFactory.h #include <string> namespace ApprovalTests { struct NamerFactory { static SectionNameDisposer appendToOutputFilename(const std::string& sectionName); }; } // ******************** From: ApprovalUtils.h #include <iosfwd> #include <string> namespace ApprovalTests { class ApprovalUtils { public: static void writeHeader(std::ostream& stream, const std::string& header); }; } // ******************** From: ApprovalComparator.h #include <string> namespace ApprovalTests { class ApprovalComparator { public: virtual ~ApprovalComparator() = default; virtual bool contentsAreEquivalent(std::string receivedPath, std::string approvedPath) const = 0; }; } // ******************** From: ComparatorDisposer.h #include <map> #include <memory> #include <utility> namespace ApprovalTests { using ComparatorContainer = std::map<std::string, std::shared_ptr<ApprovalComparator>>; class APPROVAL_TESTS_NO_DISCARD ComparatorDisposer { public: ComparatorDisposer( ComparatorContainer& comparators_, const std::string& extensionWithDot, std::shared_ptr<ApprovalTests::ApprovalComparator> previousComparator_, std::shared_ptr<ApprovalTests::ApprovalComparator> newComparator); ComparatorDisposer(const ComparatorDisposer&) = delete; ComparatorDisposer(ComparatorDisposer&& other) noexcept; ~ComparatorDisposer(); private: // A disposer becomes inactive when it is moved from. // This is done to prevent a comparator from being disposed twice. bool isActive = true; ComparatorContainer& comparators; std::string ext_; std::shared_ptr<ApprovalTests::ApprovalComparator> previousComparator; }; } // ******************** From: ComparatorFactory.h #include <memory> namespace ApprovalTests { class ComparatorFactory { private: static ComparatorContainer& comparators(); public: static ComparatorDisposer registerComparator(const std::string& extensionWithDot, std::shared_ptr<ApprovalComparator> comparator); static std::shared_ptr<ApprovalComparator> getComparatorForFile(const std::string& receivedPath); static std::shared_ptr<ApprovalComparator> getComparatorForFileExtensionWithDot(const std::string& fileExtensionWithDot); }; } // ******************** From: ApprovalWriter.h #include <string> namespace ApprovalTests { class ApprovalWriter { public: virtual ~ApprovalWriter() = default; virtual std::string getFileExtensionWithDot() const = 0; virtual void write(std::string path) const = 0; virtual void cleanUpReceived(std::string receivedPath) const = 0; }; } // ******************** From: StringWriter.h namespace ApprovalTests { class StringWriter : public ApprovalWriter { private: std::string s; std::string ext; public: explicit StringWriter(std::string contents, std::string fileExtensionWithDot = ".txt"); std::string getFileExtensionWithDot() const override; void write(std::string path) const override; void Write(std::ostream& out) const; virtual void cleanUpReceived(std::string receivedPath) const override; }; } // ******************** From: FileApprover.h #include <memory> #include <functional> namespace ApprovalTests { class FileApprover { public: FileApprover() = default; ~FileApprover() = default; /*! \brief Register a custom comparater, which will be used to compare approved * and received files with the given extension. * * @param extensionWithDot A file extention, such as ".jpg" * @param comparator <tt>std::shared_ptr</tt> to a ApprovalTests::ApprovalComparator * instance * @return A "Disposable" object. The caller should hold on to this object. * When it is destroyed, the customisation will be reversed. * * \see For more information, see * \userguide{CustomComparators,Custom Comparators} */ static ComparatorDisposer registerComparatorForExtension(const std::string& extensionWithDot, std::shared_ptr<ApprovalComparator> comparator); //! This overload is an implementation detail. To add a new comparator, use registerComparatorForExtension(). static void verify(const std::string& receivedPath, const std::string& approvedPath, const ApprovalComparator& comparator); static void verify(const std::string& receivedPath, const std::string& approvedPath); static void verify(const ApprovalNamer& n, const ApprovalWriter& s, const Reporter& r); static void reportAfterTryingFrontLoadedReporter(const std::string& receivedPath, const std::string& approvedPath, const Reporter& r); using TestPassedNotification = std::function<void()>; static void setTestPassedNotification(TestPassedNotification notification); static void notifyTestPassed(); private: static TestPassedNotification testPassedNotification_; }; } // ******************** From: FmtToString.h #ifdef FMT_VERSION namespace ApprovalTests { class FmtToString { public: template <typename T> static std::string toString(const T& printable) { (void)printable; return fmt::to_string(printable); } }; } #endif // ******************** From: FileNameSanitizerFactory.h #include <functional> namespace ApprovalTests { using FileNameSanitizer = std::function<std::string(std::string)>; class FileNameSanitizerFactory { public: static bool isForbidden(char c); static std::string defaultSanitizer(std::string fileName); static FileNameSanitizer currentSanitizer; }; } // ******************** From: FileNameSanitizerDisposer.h namespace ApprovalTests { class APPROVAL_TESTS_NO_DISCARD FileNameSanitizerDisposer { private: FileNameSanitizer previous_result; public: explicit FileNameSanitizerDisposer(FileNameSanitizer sanitizer); FileNameSanitizerDisposer(const FileNameSanitizerDisposer&) = default; ~FileNameSanitizerDisposer(); }; } // ******************** From: SubdirectoryDisposer.h #include <string> namespace ApprovalTests { //! Implementation detail of Approvals::useApprovalsSubdirectory() class APPROVAL_TESTS_NO_DISCARD SubdirectoryDisposer { private: std::string previous_result; public: explicit SubdirectoryDisposer(std::string subdirectory); SubdirectoryDisposer(const SubdirectoryDisposer&) = default; ~SubdirectoryDisposer(); }; } // ******************** From: DefaultReporterFactory.h #include <memory> namespace ApprovalTests { //! Implementation detail of Approvals::useAsDefaultReporter() class DefaultReporterFactory { private: static std::shared_ptr<Reporter>& defaultReporter(); public: static std::shared_ptr<Reporter> getDefaultReporter(); static void setDefaultReporter(const std::shared_ptr<Reporter>& reporter); }; } // ******************** From: DefaultReporterDisposer.h namespace ApprovalTests { //! Implementation detail of Approvals::useAsDefaultReporter() class APPROVAL_TESTS_NO_DISCARD DefaultReporterDisposer { private: std::shared_ptr<Reporter> previous_result; public: explicit DefaultReporterDisposer(const std::shared_ptr<Reporter>& reporter); ~DefaultReporterDisposer(); }; } // ******************** From: FirstWorkingReporter.h #include <memory> #include <vector> namespace ApprovalTests { class FirstWorkingReporter : public Reporter { private: std::vector<std::shared_ptr<Reporter>> reporters; public: // Note that FirstWorkingReporter takes ownership of the given Reporter objects explicit FirstWorkingReporter(const std::vector<Reporter*>& theReporters); explicit FirstWorkingReporter( const std::vector<std::shared_ptr<Reporter>>& reporters_); bool report(std::string received, std::string approved) const override; }; } // ******************** From: DefaultFrontLoadedReporter.h namespace ApprovalTests { class DefaultFrontLoadedReporter : public FirstWorkingReporter { public: DefaultFrontLoadedReporter(); }; } // ******************** From: FrontLoadedReporterFactory.h #include <memory> namespace ApprovalTests { //! Implementation detail of Approvals::useAsFrontLoadedReporter() class FrontLoadedReporterFactory { static std::shared_ptr<Reporter>& frontLoadedReporter(); public: static std::shared_ptr<Reporter> getFrontLoadedReporter(); static void setFrontLoadedReporter(const std::shared_ptr<Reporter>& reporter); }; } // ******************** From: FrontLoadedReporterDisposer.h namespace ApprovalTests { //! Implementation detail of Approvals::useAsFrontLoadedReporter() class APPROVAL_TESTS_NO_DISCARD FrontLoadedReporterDisposer { private: std::shared_ptr<Reporter> previous_result; public: explicit FrontLoadedReporterDisposer(const std::shared_ptr<Reporter>& reporter); FrontLoadedReporterDisposer(const FrontLoadedReporterDisposer&) = default; ~FrontLoadedReporterDisposer(); }; } // ******************** From: Scrubbers.h #include <string> #include <functional> #include <regex> namespace ApprovalTests { using Scrubber = std::function<std::string(const std::string&)>; namespace Scrubbers { std::string doNothing(const std::string& input); /**@name Regex-based scrubbers See \userguide{how_tos/ScrubNonDeterministicOutput,regular-expressions-regex,Regular Expressions (regex)} */ ///@{ using RegexMatch = std::sub_match<std::string::const_iterator>; using RegexReplacer = std::function<std::string(const RegexMatch&)>; std::string scrubRegex(const std::string& input, const std::regex& regex, const RegexReplacer& replaceFunction); Scrubber createRegexScrubber(const std::regex& regexPattern, const RegexReplacer& replacer); Scrubber createRegexScrubber(const std::regex& regexPattern, const std::string& replacementText); Scrubber createRegexScrubber(const std::string& regexString, const std::string& replacementText); ///@} std::string scrubGuid(const std::string& input); } } // ******************** From: DefaultNamerFactory.h #include <memory> #include <functional> namespace ApprovalTests { using NamerCreator = std::function<std::shared_ptr<ApprovalNamer>()>; //! Implementation detail of Approvals::useAsDefaultNamer() class DefaultNamerFactory { private: static NamerCreator& defaultNamer(); public: static NamerCreator getDefaultNamer(); static void setDefaultNamer(NamerCreator namer); }; } // ******************** From: Options.h #include <utility> #include <exception> namespace ApprovalTests { class Options { public: class FileOptions { const Options* options_ = nullptr; // set in Options::fileOptions() std::string fileExtensionWithDot_ = ".txt"; friend class Options; FileOptions() = default; explicit FileOptions(std::string fileExtensionWithDot); APPROVAL_TESTS_NO_DISCARD FileOptions clone() const; public: APPROVAL_TESTS_NO_DISCARD const std::string& getFileExtension() const; APPROVAL_TESTS_NO_DISCARD Options withFileExtension(const std::string& fileExtensionWithDot) const; }; private: FileOptions fileOptions_; Scrubber scrubber_ = Scrubbers::doNothing; const Reporter& reporter_ = defaultReporter(); std::shared_ptr<ApprovalNamer> namer_ = DefaultNamerFactory::getDefaultNamer()(); bool usingDefaultScrubber_ = true; Options(FileOptions fileOptions, Scrubber scrubber, const Reporter& reporter, bool usingDefaultScrubber, std::shared_ptr<ApprovalNamer> namer); APPROVAL_TESTS_NO_DISCARD Options clone(const FileOptions& fileOptions) const; static const Reporter& defaultReporter(); public: Options() = default; explicit Options(Scrubber scrubber); explicit Options(const Reporter& reporter); APPROVAL_TESTS_NO_DISCARD FileOptions fileOptions() const; APPROVAL_TESTS_NO_DISCARD Scrubber getScrubber() const; APPROVAL_TESTS_NO_DISCARD bool isUsingDefaultScrubber() const; APPROVAL_TESTS_NO_DISCARD std::string scrub(const std::string& input) const; APPROVAL_TESTS_NO_DISCARD const Reporter& getReporter() const; APPROVAL_TESTS_NO_DISCARD Options withReporter(const Reporter& reporter) const; APPROVAL_TESTS_NO_DISCARD Options withScrubber(Scrubber scrubber) const; APPROVAL_TESTS_NO_DISCARD std::shared_ptr<ApprovalNamer> getNamer() const; APPROVAL_TESTS_NO_DISCARD Options withNamer(std::shared_ptr<ApprovalNamer> namer); }; namespace Detail { //! Helper to prevent compilation failure when types are wrongly treated as Option // or Reporter: template <typename T, typename R = void> using EnableIfNotOptionsOrReporter = typename std::enable_if< (!std::is_same<Options, typename std::decay<T>::type>::value) && (!std::is_base_of<Reporter, typename std::decay<T>::type>::value), R>::type; //! Helper to prevent compilation failure when types are wrongly treated as Option, // Reporter or String: template <typename T, typename R = void> using EnableIfNotOptionsOrReporterOrString = typename std::enable_if< (!std::is_same<Options, typename std::decay<T>::type>::value) && (!std::is_same<std::string, typename std::decay<T>::type>::value) && (!std::is_same<char*, typename std::decay<T>::type>::value) && (!std::is_same<const char*, typename std::decay<T>::type>::value) && (!std::is_base_of<Reporter, typename std::decay<T>::type>::value), R>::type; } // namespace Detail } // ******************** From: ExistingFileNamer.h #include <utility> namespace ApprovalTests { class ExistingFileNamer : public ApprovalNamer { std::string filePath; const Options& options_; public: explicit ExistingFileNamer(std::string filePath_, const Options& options); ExistingFileNamer(const ExistingFileNamer& x); ExistingFileNamer(ExistingFileNamer&& x) noexcept; virtual std::string getApprovedFile(std::string extensionWithDot) const override; virtual std::string getReceivedFile(std::string /*extensionWithDot*/) const override; }; } // ******************** From: ExistingFile.h #include <utility> namespace ApprovalTests { class ExistingFile : public ApprovalWriter { std::string filePath; bool deleteScrubbedFile = false; const Options& options_; std::string scrub(std::string fileName, const Options& options); public: explicit ExistingFile(std::string filePath_, const Options& options); virtual std::string getFileExtensionWithDot() const override; virtual void write(std::string /*path*/) const override; virtual void cleanUpReceived(std::string receivedPath) const override; ExistingFileNamer getNamer(); }; } // ******************** From: DefaultNamerDisposer.h namespace ApprovalTests { //! Implementation detail of Approvals::useAsDefaultNamer() class APPROVAL_TESTS_NO_DISCARD DefaultNamerDisposer { private: NamerCreator previous_result; public: explicit DefaultNamerDisposer(NamerCreator namerCreator); DefaultNamerDisposer(const DefaultNamerDisposer&) = default; ~DefaultNamerDisposer(); }; } // ******************** From: Approvals.h #include <string> #include <functional> #include <exception> #include <utility> namespace ApprovalTests { // TCompileTimeOptions must have a type ToStringConverter, which must have a method toString() template <typename TCompileTimeOptions> class TApprovals { private: TApprovals() = default; ~TApprovals() = default; public: static std::shared_ptr<ApprovalNamer> getDefaultNamer() { return DefaultNamerFactory::getDefaultNamer()(); } template <typename T> using IsNotDerivedFromWriter = typename std::enable_if<!std::is_base_of<ApprovalWriter, T>::value, int>::type; /**@name Verifying single objects See \userguide{TestingSingleObjects,Testing Single Objects} */ ///@{ static void verify(const std::string& contents, const Options& options = Options()) { StringWriter writer(options.scrub(contents), options.fileOptions().getFileExtension()); FileApprover::verify(*options.getNamer(), writer, options.getReporter()); } template <typename T, typename = IsNotDerivedFromWriter<T>> static void verify(const T& contents, const Options& options = Options()) { verify(TCompileTimeOptions::ToStringConverter::toString(contents), options); } template <typename T, typename Function, typename = Detail::EnableIfNotOptionsOrReporter<Function>> static void verify(const T& contents, Function converter, const Options& options = Options()) { std::stringstream s; converter(contents, s); verify(s.str(), options); } /// Note that this overload ignores any scrubber in options static void verify(const ApprovalWriter& writer, const Options& options = Options()) { FileApprover::verify(*options.getNamer(), writer, options.getReporter()); } ///@} /**@name Verifying containers of objects - supplying an iterator range See \userguide{TestingContainers,Testing Containers} */ ///@{ template <typename Iterator> static void verifyAll(const std::string& header, const Iterator& start, const Iterator& finish, std::function<void(decltype(*start), std::ostream&)> converter, const Options& options = Options()) { std::stringstream s; ApprovalUtils::writeHeader(s, header); for (auto it = start; it != finish; ++it) { converter(*it, s); s << '\n'; } verify(s.str(), options); } ///@} /**@name Verifying containers of objects - supplying a container See \userguide{TestingContainers,Testing Containers} */ ///@{ template <typename Container> static void verifyAll( const std::string& header, const Container& list, std::function<void(typename Container::value_type, std::ostream&)> converter, const Options& options = Options()) { verifyAll<typename Container::const_iterator>( header, list.begin(), list.end(), converter, options); } template <typename Container> static void verifyAll(const std::string& header, const Container& list, const Options& options = Options()) { int i = 0; verifyAll<Container>( header, list, [&](typename Container::value_type e, std::ostream& s) { s << "[" << i++ << "] = " << TCompileTimeOptions::ToStringConverter::toString(e); }, options); } template <typename Container> static void verifyAll(const Container& list, const Options& options = Options()) { verifyAll<Container>("", list, options); } ///@} /**@name Verifying containers of objects - supplying an initializer list See \userguide{TestingContainers,Testing Containers} */ ///@{ template <typename T> static void verifyAll(const std::string& header, const std::initializer_list<T>& list, std::function<void(typename std::initializer_list<T>::value_type, std::ostream&)> converter, const Options& options = Options()) { verifyAll<std::initializer_list<T>>(header, list, converter, options); } template <typename T> static void verifyAll(const std::string& header, const std::initializer_list<T>& list, const Options& options = Options()) { verifyAll<std::initializer_list<T>>(header, list, options); } template <typename T> static void verifyAll(const std::initializer_list<T>& list, const Options& options = Options()) { verifyAll<std::initializer_list<T>>("", list, options); } ///@} /**@name Other verify methods */ ///@{ /*! \brief Verify the text of an exception See \userguide{TestingExceptions,testing-exception-messages,Testing exception messages} */ static void verifyExceptionMessage(const std::function<void(void)>& functionThatThrows, const Options& options = Options()) { std::string message = "*** no exception thrown ***"; try { functionThatThrows(); } catch (const std::exception& e) { message = e.what(); } verify(message, options); } /// Verify an existing file, that has already been written out static void verifyExistingFile(const std::string& filePath, const Options& options = Options()) { ExistingFile writer(filePath, options); FileApprover::verify(writer.getNamer(), writer, options.getReporter()); } ///@} /**@name Customising Approval Tests These static methods customise various aspects of Approval Tests behaviour. */ ///@{ /// See \userguide{Configuration,using-sub-directories-for-approved-files,Using sub-directories for approved files} static SubdirectoryDisposer useApprovalsSubdirectory(const std::string& subdirectory = "approval_tests") { return SubdirectoryDisposer(subdirectory); } /// See \userguide{Reporters,registering-a-default-reporter,Registering a default reporter} static DefaultReporterDisposer useAsDefaultReporter(const std::shared_ptr<Reporter>& reporter) { return DefaultReporterDisposer(reporter); } /// See \userguide{Reporters,front-loaded-reporters,Front Loaded Reporters} static FrontLoadedReporterDisposer useAsFrontLoadedReporter(const std::shared_ptr<Reporter>& reporter) { return FrontLoadedReporterDisposer(reporter); } /// See \userguide{Namers,registering-a-custom-namer,Registering a Custom Namer} static DefaultNamerDisposer useAsDefaultNamer(NamerCreator namerCreator) { return DefaultNamerDisposer(std::move(namerCreator)); } /// See \userguide{Namers,converting-test-names-to-valid-filenames,Converting Test Names to Valid FileNames} static FileNameSanitizerDisposer useFileNameSanitizer(FileNameSanitizer sanitizer) { return FileNameSanitizerDisposer(sanitizer); } ///@} }; #ifndef APPROVAL_TESTS_DEFAULT_STREAM_CONVERTER #define APPROVAL_TESTS_DEFAULT_STREAM_CONVERTER StringMaker #endif // Warning: Do not use CompileTimeOptions directly. // This interface is subject to change, as future // compile-time options are added. template <typename TToString> struct CompileTimeOptions { using ToStringConverter = TToString; // more template types may be added to CompileTimeOptions in future, if we add // more flexibility that requires compile-time configuration. }; // Template parameter TToString must have a method toString() // This interface will not change, as future compile-time options are added. template <typename TToString> struct ToStringCompileTimeOptions : CompileTimeOptions<TToString> { }; using Approvals = TApprovals<ToStringCompileTimeOptions<APPROVAL_TESTS_DEFAULT_STREAM_CONVERTER>>; } // ******************** From: TemplatedCustomNamer.h namespace ApprovalTests { class TemplatedCustomNamer : public ApprovalTests::ApprovalNamer { private: ApprovalTests::ApprovalTestNamer namer_; std::string template_; public: explicit TemplatedCustomNamer(std::string templateString); APPROVAL_TESTS_NO_DISCARD Path constructFromTemplate(const std::string& extensionWithDot, const std::string& approvedOrReceived) const; APPROVAL_TESTS_NO_DISCARD std::string getApprovedFile(std::string extensionWithDot) const override; APPROVAL_TESTS_NO_DISCARD std::string getReceivedFile(std::string extensionWithDot) const override; APPROVAL_TESTS_NO_DISCARD Path getApprovedFileAsPath(std::string extensionWithDot) const; APPROVAL_TESTS_NO_DISCARD Path getReceivedFileAsPath(std::string extensionWithDot) const; APPROVAL_TESTS_NO_DISCARD static std::shared_ptr<TemplatedCustomNamer> create(std::string templateString); APPROVAL_TESTS_NO_DISCARD static DefaultNamerDisposer useAsDefaultNamer(std::string templateString); }; } // ******************** From: GoogleCustomizationsFactory.h #include <vector> #include <functional> #include <string> namespace ApprovalTests { class GoogleCustomizationsFactory { public: using Comparator = std::function<bool(const std::string&, const std::string&)>; private: using ComparatorContainer = std::vector<Comparator>; static ComparatorContainer& comparatorContainer(); public: static ComparatorContainer getEquivalencyChecks(); APPROVAL_TESTS_NO_DISCARD static bool addTestCaseNameRedundancyCheck(const Comparator& comparator); }; } // ******************** From: MoreHelpMessages.h #include <string> namespace ApprovalTests { class MoreHelpMessages { public: static void deprecatedFunctionCalled(const std::string& message, const std::string& file, int lineNumber); }; } // ******************** From: CartesianProduct.h #include <functional> #include <tuple> #include <type_traits> #include <utility> #include <vector> namespace ApprovalTests { namespace CartesianProduct { namespace Detail { // C++14 compatibility // See https://en.cppreference.com/w/cpp/types/enable_if template <bool B, class T = void> using enable_if_t = typename std::enable_if<B, T>::type; // See https://en.cppreference.com/w/cpp/utility/integer_sequence template <std::size_t... Is> struct index_sequence { }; template <std::size_t N, std::size_t... Is> struct make_index_sequence : make_index_sequence<N - 1, N - 1, Is...> { }; template <std::size_t... Is> struct make_index_sequence<0, Is...> : index_sequence<Is...> { }; // End of C++14 compatibility // Return the size of a tuple - constexpr for use as a template argument // See https://en.cppreference.com/w/cpp/utility/tuple/tuple_size template <class Tuple> constexpr std::size_t tuple_size() { return std::tuple_size< typename std::remove_reference<Tuple>::type>::value; } template <class Tuple> using make_tuple_idxs = make_index_sequence<tuple_size<Tuple>()>; // C++17 compatibility // See https://en.cppreference.com/w/cpp/utility/apply template <class F, class Tuple, std::size_t... I> constexpr auto apply_impl(F&& f, Tuple&& t, index_sequence<I...>) -> decltype(std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...)) { return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...); } template <class F, class Tuple> auto apply(F&& f, Tuple&& t) -> decltype(apply_impl(std::forward<F>(f), std::forward<Tuple>(t), make_tuple_idxs<Tuple>{})) { return apply_impl( std::forward<F>(f), std::forward<Tuple>(t), make_tuple_idxs<Tuple>{}); } // End of C++17 compatibility template <class Tuple, class F, std::size_t... Is> void for_each_impl(Tuple&& t, F&& f, index_sequence<Is...>) { (void)std::initializer_list<int>{ (std::forward<F>(f)(std::get<Is>(std::forward<Tuple>(t))), 0)...}; } template <class Tuple, class F> void for_each(Tuple&& t, F&& f) { for_each_impl( std::forward<Tuple>(t), std::forward<F>(f), make_tuple_idxs<Tuple>{}); } template <class Tuple, class F, std::size_t... Is> auto transform_impl(Tuple&& t, F&& f, index_sequence<Is...>) -> decltype(std::make_tuple( std::forward<F>(f)(std::get<Is>(std::forward<Tuple>(t)))...)) { return std::make_tuple( std::forward<F>(f)(std::get<Is>(std::forward<Tuple>(t)))...); } template <class F, class Tuple> auto transform(Tuple&& t, F&& f = {}) -> decltype(transform_impl(std::forward<Tuple>(t), std::forward<F>(f), make_tuple_idxs<Tuple>{})) { return transform_impl( std::forward<Tuple>(t), std::forward<F>(f), make_tuple_idxs<Tuple>{}); } template <class Predicate> struct find_if_body { const Predicate& pred; std::size_t& index; std::size_t currentIndex = 0; bool found = false; find_if_body(const Predicate& p, std::size_t& i) : pred(p), index(i) { } template <typename T> void operator()(T&& value) { if (found) return; if (pred(std::forward<T>(value))) { index = currentIndex; found = true; } ++currentIndex; } }; template <class Predicate, class Tuple> std::size_t find_if(Tuple&& tuple, Predicate pred = {}) { std::size_t idx = tuple_size<Tuple>(); for_each(std::forward<Tuple>(tuple), find_if_body<Predicate>(pred, idx)); return idx; } template <class Predicate, class Tuple> bool any_of(Tuple&& tuple, Predicate pred = {}) { return find_if(std::forward<Tuple>(tuple), pred) != tuple_size<Tuple>(); } struct is_range_empty { template <class T> bool operator()(const T& range) const { using std::begin; using std::end; return begin(range) == end(range); } }; // Transform an iterator into a value reference which will then be passed to the visitor function: struct dereference_iterator { template <class It> auto operator()(It&& it) const -> decltype(*std::forward<It>(it)) { return *std::forward<It>(it); } }; // Increment outermost iterator. If it reaches its end, we're finished and do nothing. template <class Its, std::size_t I = tuple_size<Its>() - 1> enable_if_t<I == 0> increment_iterator(Its& it, const Its&, const Its&) { ++std::get<I>(it); } // Increment inner iterator. If it reaches its end, we reset it and increment the previous iterator. template <class Its, std::size_t I = tuple_size<Its>() - 1> enable_if_t<I != 0> increment_iterator(Its& its, const Its& begins, const Its& ends) { if (++std::get<I>(its) == std::get<I>(ends)) { std::get<I>(its) = std::get<I>(begins); increment_iterator<Its, I - 1>(its, begins, ends); } } } // namespace Detail // This is what actually loops over all the containers, one element at a time // It is called with a template type F that writes the inputs, and runs the converter, which writes the result(s) // all for one set of container values - when called by verifyAllCombinations() // More generally, F must have an operator() that acts on one set of input values. template <class F, class... Ranges> void cartesian_product(F&& f, const Ranges&... ranges) { using std::begin; using std::end; if (Detail::any_of<Detail::is_range_empty>(std::forward_as_tuple(ranges...))) return; const auto begins = std::make_tuple(begin(ranges)...); const auto ends = std::make_tuple(end(ranges)...); for (auto its = begins; std::get<0>(its) != std::get<0>(ends); Detail::increment_iterator(its, begins, ends)) { // Command-clicking on transform in CLion 2019.2.1 hangs with CLion with high CPU // 'Use clang tidy' is turned off. // Power-save turned on. // Mac Detail::apply(std::forward<F>(f), Detail::transform<Detail::dereference_iterator>(its)); } } } // namespace CartesianProduct } // namespace ApprovalTests // ******************** From: DefaultReporter.h #include <string> namespace ApprovalTests { class DefaultReporter : public Reporter { public: virtual bool report(std::string received, std::string approved) const override; }; } // ******************** From: CombinationApprovals.h #include <type_traits> #include <utility> namespace ApprovalTests { template <typename TCompileTimeOptions> class TCombinationApprovals { private: // Write out second or subsequent input value, with preceding comma and space struct print_input { std::ostream& out; template <class T> void operator()(const T& input) { out << ", " << TCompileTimeOptions::ToStringConverter::toString(input); } }; // Write out one row of output template <class Converter> struct serialize { std::ostream& out; Converter converter; template <class T, class... Ts> void operator()(T&& input1_, Ts&&... inputs) { // First value is printed without trailing comma out << "(" << TCompileTimeOptions::ToStringConverter::toString(input1_); // Remaining values are printed with prefix of a comma CartesianProduct::Detail::for_each(std::forward_as_tuple(inputs...), print_input{out}); out << ") => " << converter(input1_, inputs...) << '\n'; } }; public: /**@name Verifying combinations of objects See \userguide{TestingCombinations,Testing combinations} */ ///@{ template <class Converter, class Container, class... Containers> static void verifyAllCombinations(const Options& options, const std::string& header, Converter&& converter, const Container& input0, const Containers&... inputs) { std::stringstream s; ApprovalUtils::writeHeader(s, header); CartesianProduct::cartesian_product( serialize<Converter>{s, std::forward<Converter>(converter)}, input0, inputs...); Approvals::verify(s.str(), options); } template <class Converter, class... Containers> ApprovalTests::Detail::EnableIfNotOptionsOrReporterOrString< Converter> static verifyAllCombinations(const std::string& header, Converter&& converter, const Containers&... inputs) { verifyAllCombinations( Options(), header, std::forward<Converter>(converter), inputs...); } template <class Converter, class... Containers> ApprovalTests::Detail::EnableIfNotOptionsOrReporterOrString< Converter> static verifyAllCombinations(const Options& options, Converter&& converter, const Containers&... inputs) { verifyAllCombinations( options, std::string(), std::forward<Converter>(converter), inputs...); } template <class Converter, class... Containers> ApprovalTests::Detail::EnableIfNotOptionsOrReporterOrString< Converter> static verifyAllCombinations(Converter&& converter, const Containers&... inputs) { verifyAllCombinations( Options(), std::string(), std::forward<Converter>(converter), inputs...); } ///@} }; using CombinationApprovals = TCombinationApprovals< ToStringCompileTimeOptions<APPROVAL_TESTS_DEFAULT_STREAM_CONVERTER>>; } // namespace ApprovalTests // ******************** From: Storyboard.h #include <iosfwd> #include <sstream> #include <functional> namespace ApprovalTests { class Storyboard { private: std::stringstream output_; int frameCount_ = 0; bool addNewLineBeforeNextFrame_ = false; public: Storyboard& addDescription(const std::string& description); Storyboard& addDescriptionWithData(const std::string& description, const std::string& data); Storyboard& addFrame(const std::string& frame); Storyboard& addFrame(const std::string& title, const std::string& frame); Storyboard& addFrames(int numberOfFrames, const std::function<std::string(int)>& function); friend std::ostream& operator<<(std::ostream& os, const Storyboard& board); }; } // ******************** From: TextFileComparator.h #include <fstream> namespace ApprovalTests { class TextFileComparator : public ApprovalComparator { public: static std::ifstream::int_type getNextRelevantCharacter(std::ifstream& astream); virtual bool contentsAreEquivalent(std::string receivedPath, std::string approvedPath) const override; }; } // ******************** From: ApprovalException.h #include <exception> #include <string> namespace ApprovalTests { class ApprovalException : public std::exception { private: std::string message; public: explicit ApprovalException(const std::string& msg); virtual const char* what() const noexcept override; }; class ApprovalMismatchException : public ApprovalException { private: std::string format(const std::string& received, const std::string& approved); public: ApprovalMismatchException(const std::string& received, const std::string& approved); }; class ApprovalMissingException : public ApprovalException { private: std::string format(const std::string& file); public: ApprovalMissingException(const std::string& /*received*/, const std::string& approved); }; } // ******************** From: FrameworkIntegrations.h namespace ApprovalTests { class FrameworkIntegrations { public: static void setTestPassedNotification(FileApprover::TestPassedNotification notification); static void setCurrentTest(ApprovalTests::TestName* currentTest); }; } // ******************** From: BoostTestApprovals.h #ifdef APPROVALS_BOOSTTEST #define APPROVAL_TESTS_INCLUDE_CPPS namespace ApprovalTests { class BoostApprovalListener : public boost::unit_test::test_observer { ApprovalTests::TestName currentTest; void test_unit_start(boost::unit_test::test_unit const& test) override { std::string path(test.p_file_name.begin(), test.p_file_name.end()); currentTest.setFileName(path); currentTest.sections.push_back(test.p_name); ApprovalTests::FrameworkIntegrations::setCurrentTest(¤tTest); ApprovalTests::FrameworkIntegrations::setTestPassedNotification( []() { BOOST_CHECK(true); }); } void test_unit_finish(boost::unit_test::test_unit const& /*test*/, unsigned long) override { currentTest.sections.pop_back(); } }; int register_our_listener(BoostApprovalListener& t) { boost::unit_test::framework::register_observer(t); return 1; } BoostApprovalListener o; auto dummy_variable = register_our_listener(o); } #endif // APPROVALS_BOOSTTEST // ******************** From: Catch2Approvals.h #if defined(APPROVALS_CATCH_EXISTING_MAIN) #define APPROVALS_CATCH #define CATCH_CONFIG_RUNNER #elif defined(APPROVALS_CATCH) #define CATCH_CONFIG_MAIN #endif #ifdef APPROVALS_CATCH #define APPROVAL_TESTS_INCLUDE_CPPS #include <catch2/catch.hpp> //namespace ApprovalTests { struct Catch2ApprovalListener : Catch::TestEventListenerBase { ApprovalTests::TestName currentTest; using TestEventListenerBase:: TestEventListenerBase; // This using allows us to use all base-class constructors virtual void testCaseStarting(Catch::TestCaseInfo const& testInfo) override { currentTest.setFileName(testInfo.lineInfo.file); ApprovalTests::FrameworkIntegrations::setCurrentTest(¤tTest); ApprovalTests::FrameworkIntegrations::setTestPassedNotification( []() { REQUIRE(true); }); } virtual void testCaseEnded(Catch::TestCaseStats const& /*testCaseStats*/) override { while (!currentTest.sections.empty()) { currentTest.sections.pop_back(); } } virtual void sectionStarting(Catch::SectionInfo const& sectionInfo) override { currentTest.sections.push_back(sectionInfo.name); } virtual void sectionEnded(Catch::SectionStats const& /*sectionStats*/) override { currentTest.sections.pop_back(); } }; //} CATCH_REGISTER_LISTENER(Catch2ApprovalListener) #endif #ifdef TEST_COMMIT_REVERT_CATCH //namespace ApprovalTests { struct Catch2TestCommitRevert : Catch::TestEventListenerBase { using TestEventListenerBase:: TestEventListenerBase; // This using allows us to use all base-class constructors virtual void testRunEnded(Catch::TestRunStats const& testRunStats) override { bool commit = testRunStats.totals.testCases.allOk(); std::string message = "r "; if (commit) { std::cout << "git add -A \n"; std::cout << "git commit -m " << message; } else { std::cout << "git clean -fd \n"; std::cout << "git reset --hard HEAD \n"; } } }; //} CATCH_REGISTER_LISTENER(Catch2TestCommitRevert) #endif // ******************** From: CppUTestApprovals.h #ifdef APPROVALS_CPPUTEST_EXISTING_MAIN #define APPROVALS_CPPUTEST #endif #ifdef APPROVALS_CPPUTEST #define APPROVAL_TESTS_INCLUDE_CPPS #include <CppUTest/CommandLineTestRunner.h> #include <CppUTest/TestPlugin.h> #include <CppUTest/TestRegistry.h> namespace ApprovalTests { class ApprovalTestsCppUTestPlugin : public TestPlugin { private: // We need to be able to delete currentTest at the end of the // test, to prevent CppUTest's leak-checking from triggering, // due to an undeleted std::string - so we use std::unique_ptr. std::unique_ptr<ApprovalTests::TestName> currentTest; public: ApprovalTestsCppUTestPlugin() : TestPlugin("ApprovalTestsCppUTestPlugin") { // Turn off CppUTest's leak checks. // On some platforms, CppUTest's leak-checking reports leaks // in this code, because the way the platform's std::string manages life-times // of string storage is not compatible with the requirements of the // CppUTest leak-checks. MemoryLeakWarningPlugin::turnOffNewDeleteOverloads(); } APPROVAL_TESTS_NO_DISCARD static std::string cppUTestToString(const SimpleString& string) { return std::string{string.asCharString()}; } void preTestAction(UtestShell& shell, TestResult& result) override { currentTest.reset(new ApprovalTests::TestName); currentTest->setFileName(cppUTestToString(shell.getFile())); currentTest->sections.emplace_back(cppUTestToString(shell.getGroup())); currentTest->sections.emplace_back(cppUTestToString(shell.getName())); ApprovalTests::FrameworkIntegrations::setCurrentTest(currentTest.get()); ApprovalTests::FrameworkIntegrations::setTestPassedNotification( []() { CHECK_TRUE(true); }); TestPlugin::preTestAction(shell, result); } void postTestAction(UtestShell& shell, TestResult& result) override { currentTest = nullptr; TestPlugin::postTestAction(shell, result); } }; inline void initializeApprovalTestsForCppUTest() { static ApprovalTests::ApprovalTestsCppUTestPlugin logPlugin; TestRegistry::getCurrentRegistry()->installPlugin(&logPlugin); } } #ifndef APPROVALS_CPPUTEST_EXISTING_MAIN int main(int argc, char** argv) { ApprovalTests::initializeApprovalTestsForCppUTest(); int result = CommandLineTestRunner::RunAllTests(argc, argv); TestRegistry::getCurrentRegistry()->resetPlugins(); return result; } #endif #endif // APPROVALS_CPPUTEST // ******************** From: DocTestApprovals.h #if defined(APPROVALS_DOCTEST_EXISTING_MAIN) #define APPROVALS_DOCTEST #define DOCTEST_CONFIG_IMPLEMENT #elif defined(APPROVALS_DOCTEST) #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #endif #ifdef APPROVALS_DOCTEST #define APPROVAL_TESTS_INCLUDE_CPPS #include <doctest/doctest.h> namespace ApprovalTests { // anonymous namespace to prevent compiler -Wsubobject-linkage warnings // This is OK as this code is only compiled on main() namespace { struct AbstractReporter : doctest::IReporter { virtual void report_query(const doctest::QueryData&) override { } // called when the whole test run starts virtual void test_run_start() override { } // called when the whole test run ends (caching a pointer to the input doesn't make sense here) virtual void test_run_end(const doctest::TestRunStats&) override { } // called when a test case is started (safe to cache a pointer to the input) virtual void test_case_start(const doctest::TestCaseData&) override { } #if 20305 <= DOCTEST_VERSION // called when a test case is reentered because of unfinished subcases (safe to cache a pointer to the input) virtual void test_case_reenter(const doctest::TestCaseData&) override { } #endif // called when a test case has ended virtual void test_case_end(const doctest::CurrentTestCaseStats&) override { } // called when an exception is thrown from the test case (or it crashes) virtual void test_case_exception(const doctest::TestCaseException&) override { } // called whenever a subcase is entered (don't cache pointers to the input) virtual void subcase_start(const doctest::SubcaseSignature&) override { } // called whenever a subcase is exited (don't cache pointers to the input) virtual void subcase_end() override { } // called for each assert (don't cache pointers to the input) virtual void log_assert(const doctest::AssertData&) override { } // called for each message (don't cache pointers to the input) virtual void log_message(const doctest::MessageData&) override { } // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator // or isn't in the execution range (between first and last) (safe to cache a pointer to the input) virtual void test_case_skipped(const doctest::TestCaseData&) override { } }; struct DocTestApprovalListener : AbstractReporter { TestName currentTest; // constructor has to accept the ContextOptions by ref as a single argument explicit DocTestApprovalListener(const doctest::ContextOptions& /*in*/) { } std::string doctestToString(const doctest::String& string) const { return string.c_str(); } std::string doctestToString(const char* string) const { return string; } void test_case_start(const doctest::TestCaseData& testInfo) override { currentTest.sections.emplace_back(testInfo.m_name); currentTest.setFileName(doctestToString(testInfo.m_file)); ApprovalTests::FrameworkIntegrations::setCurrentTest(¤tTest); ApprovalTests::FrameworkIntegrations::setTestPassedNotification( []() { REQUIRE(true); }); } void test_case_end(const doctest::CurrentTestCaseStats& /*in*/) override { while (!currentTest.sections.empty()) { currentTest.sections.pop_back(); } } void subcase_start(const doctest::SubcaseSignature& signature) override { currentTest.sections.emplace_back(doctestToString(signature.m_name)); } void subcase_end() override { currentTest.sections.pop_back(); } }; } } REGISTER_LISTENER("approvals", 0, ApprovalTests::DocTestApprovalListener); #endif // APPROVALS_DOCTEST // ******************** From: FmtApprovals.h #ifdef FMT_VERSION namespace ApprovalTests { using FmtApprovals = ApprovalTests::TApprovals<ApprovalTests::ToStringCompileTimeOptions<FmtToString>>; } #endif // ******************** From: GoogleConfiguration.h namespace ApprovalTests { class GoogleConfiguration { public: // This result is not used, it is only there to allow the method to execute, when this is used outside a function. APPROVAL_TESTS_NO_DISCARD static bool addTestCaseNameRedundancyCheck( GoogleCustomizationsFactory::Comparator comparator); // This result is not used, it is only there to allow the method to execute, when this is used outside a function. APPROVAL_TESTS_NO_DISCARD static bool addIgnorableTestCaseNameSuffix(std::string suffix); static GoogleCustomizationsFactory::Comparator createIgnorableTestCaseNameSuffixCheck(const std::string& suffix); }; } // ******************** From: GoogleTestApprovals.h #ifdef APPROVALS_GOOGLETEST_EXISTING_MAIN #define APPROVALS_GOOGLETEST #endif #ifdef APPROVALS_GOOGLETEST #define APPROVAL_TESTS_INCLUDE_CPPS #include <gtest/gtest.h> namespace ApprovalTests { class GoogleTestListener : public testing::EmptyTestEventListener { TestName currentTest; public: bool isDuplicate(std::string testFileNameWithExtension, std::string testCaseName) { for (auto check : GoogleCustomizationsFactory::getEquivalencyChecks()) { if (check(testFileNameWithExtension, testCaseName)) { return true; } } return false; } virtual void OnTestStart(const testing::TestInfo& testInfo) override { currentTest.setFileName(testInfo.file()); currentTest.sections = {}; if (!isDuplicate(currentTest.getFileName(), testInfo.test_case_name())) { currentTest.sections.emplace_back(testInfo.test_case_name()); } if (!std::string(testInfo.name()).empty()) { currentTest.sections.emplace_back(testInfo.name()); } ApprovalTests::FrameworkIntegrations::setCurrentTest(¤tTest); ApprovalTests::FrameworkIntegrations::setTestPassedNotification( []() { EXPECT_TRUE(true); }); } }; inline void initializeApprovalTestsForGoogleTests() { auto& listeners = testing::UnitTest::GetInstance()->listeners(); listeners.Append(new GoogleTestListener); } } #ifndef APPROVALS_GOOGLETEST_EXISTING_MAIN int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); ApprovalTests::initializeApprovalTestsForGoogleTests(); return RUN_ALL_TESTS(); } #endif //APPROVALS_GOOGLETEST_EXISTING_MAIN #endif // ******************** From: UTApprovals.h #ifdef APPROVALS_UT #define APPROVAL_TESTS_INCLUDE_CPPS #if !(__GNUC__ >= 9 or __clang_major__ >= 9) #error \ "The [Boost].UT integration with Approval Tests requires source_location support by the compiler" #endif #include <boost/ut.hpp> namespace ApprovalTests { namespace cfg { void notify_success(); class reporter : public boost::ut::reporter<boost::ut::printer> { private: TestName currentTest; public: auto on(boost::ut::events::test_begin test_begin) -> void { std::string name = std::string(test_begin.name); currentTest.sections.emplace_back(name); currentTest.setFileName(test_begin.location.file_name()); ApprovalTests::FrameworkIntegrations::setCurrentTest(¤tTest); ApprovalTests::FrameworkIntegrations::setTestPassedNotification( []() { notify_success(); }); boost::ut::reporter<boost::ut::printer>::on(test_begin); } auto on(boost::ut::events::test_run test_run) -> void { boost::ut::reporter<boost::ut::printer>::on(test_run); } auto on(boost::ut::events::test_skip test_skip) -> void { boost::ut::reporter<boost::ut::printer>::on(test_skip); } auto on(boost::ut::events::test_end test_end) -> void { while (!currentTest.sections.empty()) { currentTest.sections.pop_back(); } boost::ut::reporter<boost::ut::printer>::on(test_end); } template <class TMsg> auto on(boost::ut::events::log<TMsg> log) -> void { boost::ut::reporter<boost::ut::printer>::on(log); } template <class TExpr> auto on(boost::ut::events::assertion_pass<TExpr> location) -> void { boost::ut::reporter<boost::ut::printer>::on(location); } template <class TExpr> auto on(boost::ut::events::assertion_fail<TExpr> fail) -> void { boost::ut::reporter<boost::ut::printer>::on(fail); } auto on(boost::ut::events::fatal_assertion fatal) -> void { boost::ut::reporter<boost::ut::printer>::on(fatal); } auto on(boost::ut::events::exception exception) -> void { boost::ut::reporter<boost::ut::printer>::on(exception); } auto on(boost::ut::events::summary summary) -> void { boost::ut::reporter<boost::ut::printer>::on(summary); } }; } // namespace cfg } template <> auto boost::ut::cfg<boost::ut::override> = boost::ut::runner<ApprovalTests::cfg::reporter>{}; namespace ApprovalTests { namespace cfg { void notify_success() { // This needs to be after the registering of our custom listener, // for compilation to succeed. boost::ut::expect(true); } } } #endif // APPROVALS_UT // ******************** From: HelpMessages.h #include <string> #include <vector> namespace ApprovalTests { class HelpMessages { public: static std::string getMisconfiguredBuildHelp(const std::string& fileName); static std::string getMisconfiguredMainHelp(); static std::string getUnconfiguredRootDirectory(); static std::string getUnknownEnvVarReporterHelp(const std::string& envVarName, const std::string& selected, const std::vector<std::string>& knowns); static std::string getInvalidEnvVarReporterHelp(const std::string& envVarName, const std::string& selected, const std::vector<std::string>& knowns); static std::string envVarErrorMessage(const std::string& envVarName, const std::string& selected, const std::vector<std::string>& knowns, std::string& helpMessage); static std::string topAndTailHelpMessage(const std::string& message); }; } // ******************** From: SeparateApprovedAndReceivedDirectoriesNamer.h namespace ApprovalTests { class SeparateApprovedAndReceivedDirectoriesNamer : public TemplatedCustomNamer { public: SeparateApprovedAndReceivedDirectoriesNamer(); virtual ~SeparateApprovedAndReceivedDirectoriesNamer() override = default; static DefaultNamerDisposer useAsDefaultNamer(); }; } // ******************** From: AutoApproveIfMissingReporter.h namespace ApprovalTests { class AutoApproveIfMissingReporter : public Reporter { public: bool report(std::string received, std::string approved) const override; }; } // ******************** From: BlockingReporter.h #include <memory> #include <utility> namespace ApprovalTests { class BlockingReporter : public Reporter { private: std::shared_ptr<Blocker> blocker; BlockingReporter() = delete; public: explicit BlockingReporter(std::shared_ptr<Blocker> blocker_); static std::shared_ptr<BlockingReporter> onMachineNamed(const std::string& machineName); static std::shared_ptr<BlockingReporter> onMachinesNotNamed(const std::string& machineName); virtual bool report(std::string /*received*/, std::string /*approved*/) const override; }; } // ******************** From: CIBuildOnlyReporter.h #include <memory> namespace ApprovalTests { // A reporter which uses the supplied reporter, if called on a supported Continuous Integration system class CIBuildOnlyReporter : public Reporter { private: std::shared_ptr<Reporter> m_reporter; public: explicit CIBuildOnlyReporter( std::shared_ptr<Reporter> reporter = std::make_shared<TextDiffReporter>()); bool report(std::string received, std::string approved) const override; static bool isRunningUnderCI(); }; } // namespace ApprovalTests // ******************** From: CIBuildOnlyReporterUtils.h namespace ApprovalTests { namespace CIBuildOnlyReporterUtils { FrontLoadedReporterDisposer useAsFrontLoadedReporter(const std::shared_ptr<Reporter>& reporter); } } // namespace ApprovalTests // ******************** From: ClipboardReporter.h #include <string> namespace ApprovalTests { class ClipboardReporter : public Reporter { public: static std::string getCommandLineFor(const std::string& received, const std::string& approved, bool isWindows); virtual bool report(std::string received, std::string approved) const override; static void copyToClipboard(const std::string& newClipboard); }; } // ******************** From: CombinationReporter.h #include <memory> #include <vector> namespace ApprovalTests { class CombinationReporter : public Reporter { private: std::vector<std::unique_ptr<Reporter>> reporters; public: // Note that CombinationReporter takes ownership of the given Reporter objects explicit CombinationReporter(const std::vector<Reporter*>& theReporters); bool report(std::string received, std::string approved) const override; }; } // ******************** From: CustomReporter.h #include <memory> namespace ApprovalTests { class CustomReporter { public: static std::shared_ptr<GenericDiffReporter> create(std::string path, Type type = Type::TEXT); static std::shared_ptr<GenericDiffReporter> create(std::string path, std::string arguments, Type type = Type::TEXT); static std::shared_ptr<GenericDiffReporter> createForegroundReporter( std::string path, Type type = Type::TEXT, bool allowNonZeroExitCodes = false); static std::shared_ptr<GenericDiffReporter> createForegroundReporter(std::string path, std::string arguments, Type type = Type::TEXT, bool allowNonZeroExitCodes = false); }; } // ******************** From: DiffReporter.h namespace ApprovalTests { class DiffReporter : public FirstWorkingReporter { public: DiffReporter(); }; } // ******************** From: EnvironmentVariableReporter.h #include <memory> namespace ApprovalTests { class EnvironmentVariableReporter : public Reporter { public: bool report(std::string received, std::string approved) const override; bool report(const std::string& envVar, const std::string& received, const std::string& approved) const; static std::string environmentVariableName(); private: ReporterFactory factory; }; } // ******************** From: LinuxReporters.h namespace ApprovalTests { namespace Linux { class SublimeMergeSnapReporter : public GenericDiffReporter { public: SublimeMergeSnapReporter(); }; class SublimeMergeFlatpakReporter : public GenericDiffReporter { public: SublimeMergeFlatpakReporter(); }; class SublimeMergeRepositoryPackageReporter : public GenericDiffReporter { public: SublimeMergeRepositoryPackageReporter(); }; class SublimeMergeDirectDownloadReporter : public GenericDiffReporter { public: SublimeMergeDirectDownloadReporter(); }; class SublimeMergeReporter : public FirstWorkingReporter { public: SublimeMergeReporter(); }; class KDiff3Reporter : public GenericDiffReporter { public: KDiff3Reporter(); }; class MeldReporter : public GenericDiffReporter { public: MeldReporter(); }; class BeyondCompareReporter : public GenericDiffReporter { public: BeyondCompareReporter(); }; class LinuxDiffReporter : public FirstWorkingReporter { public: LinuxDiffReporter(); }; } } // ******************** From: MacReporters.h namespace ApprovalTests { namespace Mac { class DiffMergeReporter : public GenericDiffReporter { public: DiffMergeReporter(); }; class AraxisMergeReporter : public GenericDiffReporter { public: AraxisMergeReporter(); }; class VisualStudioCodeReporter : public GenericDiffReporter { public: VisualStudioCodeReporter(); }; class BeyondCompareReporter : public GenericDiffReporter { public: BeyondCompareReporter(); }; class KaleidoscopeReporter : public GenericDiffReporter { public: KaleidoscopeReporter(); }; class SublimeMergeReporter : public GenericDiffReporter { public: SublimeMergeReporter(); }; class KDiff3Reporter : public GenericDiffReporter { public: KDiff3Reporter(); }; class P4MergeReporter : public GenericDiffReporter { public: P4MergeReporter(); }; class TkDiffReporter : public GenericDiffReporter { public: TkDiffReporter(); }; // Note that this will be found on Linux too. // See https://github.com/approvals/ApprovalTests.cpp/issues/138 for limitations class CLionDiffReporter : public GenericDiffReporter { public: CLionDiffReporter(); }; class MacDiffReporter : public FirstWorkingReporter { public: MacDiffReporter(); bool report(std::string received, std::string approved) const override; }; } } // ******************** From: WindowsReporters.h namespace ApprovalTests { namespace Windows { class VisualStudioCodeReporter : public GenericDiffReporter { public: VisualStudioCodeReporter(); }; // ----------------------- Beyond Compare ---------------------------------- class BeyondCompare3Reporter : public GenericDiffReporter { public: BeyondCompare3Reporter(); }; class BeyondCompare4Reporter : public GenericDiffReporter { public: BeyondCompare4Reporter(); }; class BeyondCompareReporter : public FirstWorkingReporter { public: BeyondCompareReporter(); }; // ----------------------- Tortoise SVN ------------------------------------ class TortoiseImageDiffReporter : public GenericDiffReporter { public: TortoiseImageDiffReporter(); }; class TortoiseTextDiffReporter : public GenericDiffReporter { public: TortoiseTextDiffReporter(); }; class TortoiseDiffReporter : public FirstWorkingReporter { public: TortoiseDiffReporter(); }; // ----------------------- Tortoise Git ------------------------------------ class TortoiseGitTextDiffReporter : public GenericDiffReporter { public: TortoiseGitTextDiffReporter(); }; class TortoiseGitImageDiffReporter : public GenericDiffReporter { public: TortoiseGitImageDiffReporter(); }; class TortoiseGitDiffReporter : public FirstWorkingReporter { public: TortoiseGitDiffReporter(); }; // ------------------------------------------------------------------------- class WinMergeReporter : public GenericDiffReporter { public: WinMergeReporter(); }; class AraxisMergeReporter : public GenericDiffReporter { public: AraxisMergeReporter(); }; class CodeCompareReporter : public GenericDiffReporter { public: CodeCompareReporter(); }; class SublimeMergeReporter : public GenericDiffReporter { public: SublimeMergeReporter(); }; class KDiff3Reporter : public GenericDiffReporter { public: KDiff3Reporter(); }; class WindowsDiffReporter : public FirstWorkingReporter { public: WindowsDiffReporter(); bool report(std::string received, std::string approved) const override; }; } } // ******************** From: DateUtils.h #include <chrono> #include <string> namespace ApprovalTests { // All values are in UTC class DateUtils { public: static std::tm createTm(int year, int month, int day, int hour, int minute, int second); static std::chrono::system_clock::time_point createUtcDateTime(int year, int month, int day, int hour, int minute, int second); static std::string toString(const std::chrono::system_clock::time_point& dateTime, const std::string& format); static std::string toString(const std::chrono::system_clock::time_point& dateTime); static time_t toUtc(std::tm& timeinfo); static tm toUtc(time_t& tt); }; } // ******************** From: EmptyFileCreatorByType.h #include <map> #include <string> namespace ApprovalTests { class EmptyFileCreatorByType { private: static std::map<std::string, EmptyFileCreator> creators_; public: static void registerCreator(const std::string& extensionWithDot, EmptyFileCreator creator); static void createFile(const std::string& fileName); }; } // ******************** From: ExceptionCollector.h #include <sstream> #include <exception> #include <string> #include <vector> #include <functional> namespace ApprovalTests { class ExceptionCollector { std::vector<std::string> exceptionMessages; public: void gather(std::function<void(void)> functionThatThrows); ~ExceptionCollector(); void release(); }; } // ******************** From: FileUtilsSystemSpecific.h namespace ApprovalTests { class FileUtilsSystemSpecific { public: static std::string getCommandLineForCopy(const std::string& source, const std::string& destination, bool isWindows); static void copyFile(const std::string& source, const std::string& destination); }; } // ******************** From: Grid.h #include <sstream> #include <functional> namespace ApprovalTests { class Grid { public: static std::string print(int width, int height, std::function<void(int, int, std::ostream&)> printCell); static std::string print(int width, int height, std::string text); }; } #ifdef APPROVAL_TESTS_INCLUDE_CPPS // ******************** From: ApprovalUtils.cpp #include <ostream> namespace ApprovalTests { void ApprovalUtils::writeHeader(std::ostream& stream, const std::string& header) { if (!header.empty()) { stream << header << "\n\n\n"; } } } // ******************** From: Storyboard.cpp namespace ApprovalTests { Storyboard& Storyboard::addDescription(const std::string& description) { output_ << description << "\n"; addNewLineBeforeNextFrame_ = true; return *this; } Storyboard& Storyboard::addDescriptionWithData(const std::string& description, const std::string& data) { output_ << description << ": " << data << "\n"; addNewLineBeforeNextFrame_ = true; return *this; } Storyboard& Storyboard::addFrame(const std::string& frame) { if (frameCount_ == 0) { return addFrame("Initial Frame", frame); } else { return addFrame("Frame #" + std::to_string(frameCount_), frame); } } Storyboard& Storyboard::addFrame(const std::string& title, const std::string& frame) { if (addNewLineBeforeNextFrame_) { output_ << '\n'; addNewLineBeforeNextFrame_ = false; } output_ << title << ":\n"; output_ << frame << "\n\n"; frameCount_ += 1; return *this; } Storyboard& Storyboard::addFrames(int numberOfFrames, const std::function<std::string(int)>& function) { for (int frame = 1; frame <= numberOfFrames; ++frame) { addFrame(function(frame)); } return *this; } std::ostream& operator<<(std::ostream& os, const Storyboard& board) { os << board.output_.str(); return os; } } // ******************** From: ComparatorDisposer.cpp namespace ApprovalTests { ComparatorDisposer::ComparatorDisposer( ComparatorContainer& comparators_, const std::string& extensionWithDot, std::shared_ptr<ApprovalTests::ApprovalComparator> previousComparator_, std::shared_ptr<ApprovalTests::ApprovalComparator> newComparator) : comparators(comparators_) , ext_(extensionWithDot) , previousComparator(std::move(previousComparator_)) { comparators_[extensionWithDot] = std::move(newComparator); } ComparatorDisposer::ComparatorDisposer(ComparatorDisposer&& other) noexcept : comparators(other.comparators) , ext_(std::move(other.ext_)) , previousComparator(std::move(other.previousComparator)) { other.isActive = false; } ComparatorDisposer::~ComparatorDisposer() { if (isActive) { comparators[ext_] = previousComparator; } } } // ******************** From: ComparatorFactory.cpp namespace ApprovalTests { ComparatorContainer& ComparatorFactory::comparators() { static ComparatorContainer allComparators; return allComparators; } ComparatorDisposer ComparatorFactory::registerComparator(const std::string& extensionWithDot, std::shared_ptr<ApprovalComparator> comparator) { return ComparatorDisposer(comparators(), extensionWithDot, getComparatorForFileExtensionWithDot(extensionWithDot), comparator); } std::shared_ptr<ApprovalComparator> ComparatorFactory::getComparatorForFile(const std::string& receivedPath) { const std::string fileExtension = FileUtils::getExtensionWithDot(receivedPath); return getComparatorForFileExtensionWithDot(fileExtension); } std::shared_ptr<ApprovalComparator> ComparatorFactory::getComparatorForFileExtensionWithDot( const std::string& fileExtensionWithDot) { auto iterator = comparators().find(fileExtensionWithDot); if (iterator != comparators().end()) { return iterator->second; } return std::make_shared<TextFileComparator>(); } } // ******************** From: TextFileComparator.cpp #include <fstream> namespace ApprovalTests { std::ifstream::int_type TextFileComparator::getNextRelevantCharacter(std::ifstream& astream) { auto ch = astream.get(); if (ch == '\r') { return astream.get(); } else { return ch; } } bool TextFileComparator::contentsAreEquivalent(std::string receivedPath, std::string approvedPath) const { std::ifstream astream(approvedPath.c_str(), std::ios::binary | std::ifstream::in); std::ifstream rstream(receivedPath.c_str(), std::ios::binary | std::ifstream::in); while (astream.good() && rstream.good()) { int a = getNextRelevantCharacter(astream); int r = getNextRelevantCharacter(rstream); if (a != r) { return false; } } return true; } } // ******************** From: ApprovalException.cpp #include <sstream> namespace ApprovalTests { ApprovalException::ApprovalException(const std::string& msg) : message(msg) { } const char* ApprovalException::what() const noexcept { return message.c_str(); } std::string ApprovalMismatchException::format(const std::string& received, const std::string& approved) { std::stringstream s; s << "Failed Approval: \n" << "Received does not match approved \n" << "Received : \"" << received << "\" \n" << "Approved : \"" << approved << "\""; return s.str(); } ApprovalMismatchException::ApprovalMismatchException(const std::string& received, const std::string& approved) : ApprovalException(format(received, approved)) { } std::string ApprovalMissingException::format(const std::string& file) { std::stringstream s; s << "Failed Approval: \n" << "Approval File Not Found \n" << "File: \"" << file << '"'; return s.str(); } ApprovalMissingException::ApprovalMissingException(const std::string&, const std::string& approved) : ApprovalException(format(approved)) { } } // ******************** From: FileApprover.cpp #include <sstream> namespace ApprovalTests { ComparatorDisposer FileApprover::registerComparatorForExtension( const std::string& extensionWithDot, std::shared_ptr<ApprovalComparator> comparator) { return ComparatorFactory::registerComparator(extensionWithDot, comparator); } FileApprover::TestPassedNotification FileApprover::testPassedNotification_ = []() {}; void FileApprover::verify(const std::string& receivedPath, const std::string& approvedPath, const ApprovalComparator& comparator) { if (receivedPath == approvedPath) { std::stringstream s; s << "Identical filenames for received and approved.\n" << "Tests would spuriously pass. \n" << "Please check your custom namer. \n" << "Received : \"" << receivedPath << "\" \n" << "Approved : \"" << approvedPath << "\""; // Do not throw ApprovalException, as we don't want reporters to trigger. // They would show two seemingly identical files, due to matching file name. throw std::runtime_error(s.str()); } if (!FileUtils::fileExists(approvedPath)) { throw ApprovalMissingException(receivedPath, approvedPath); } if (!FileUtils::fileExists(receivedPath)) { throw ApprovalMissingException(approvedPath, receivedPath); } if (!comparator.contentsAreEquivalent(receivedPath, approvedPath)) { throw ApprovalMismatchException(receivedPath, approvedPath); } } void FileApprover::verify(const std::string& receivedPath, const std::string& approvedPath) { verify(receivedPath, approvedPath, *ComparatorFactory::getComparatorForFile(receivedPath)); } void FileApprover::verify(const ApprovalNamer& n, const ApprovalWriter& s, const Reporter& r) { std::string approvedPath = n.getApprovedFile(s.getFileExtensionWithDot()); std::string receivedPath = n.getReceivedFile(s.getFileExtensionWithDot()); SystemUtils::ensureParentDirectoryExists(approvedPath); SystemUtils::ensureParentDirectoryExists(receivedPath); s.write(receivedPath); try { verify(receivedPath, approvedPath); s.cleanUpReceived(receivedPath); notifyTestPassed(); } catch (const ApprovalException&) { reportAfterTryingFrontLoadedReporter(receivedPath, approvedPath, r); throw; } } void FileApprover::reportAfterTryingFrontLoadedReporter(const std::string& receivedPath, const std::string& approvedPath, const Reporter& r) { auto tryFirst = FrontLoadedReporterFactory::getFrontLoadedReporter(); if (!tryFirst->report(receivedPath, approvedPath)) { r.report(receivedPath, approvedPath); } } void FileApprover::setTestPassedNotification( FileApprover::TestPassedNotification notification) { testPassedNotification_ = notification; } void FileApprover::notifyTestPassed() { testPassedNotification_(); } } // ******************** From: Options.cpp namespace ApprovalTests { // FileOptions ----------------------------------------------------------------------- Options::FileOptions::FileOptions(std::string fileExtensionWithDot) : fileExtensionWithDot_(std::move(fileExtensionWithDot)) { } Options::FileOptions Options::FileOptions::clone() const { // the returned options_ must be null return FileOptions(fileExtensionWithDot_); } const std::string& Options::FileOptions::getFileExtension() const { return fileExtensionWithDot_; } Options Options::FileOptions::withFileExtension(const std::string& fileExtensionWithDot) const { FileOptions newSelf(fileExtensionWithDot); return options_->clone(newSelf); } // Options --------------------------------------------------------------------------- Options::Options(Options::FileOptions fileOptions, Scrubber scrubber, const Reporter& reporter, bool usingDefaultScrubber, std::shared_ptr<ApprovalNamer> namer) : fileOptions_(std::move(fileOptions)) , scrubber_(std::move(scrubber)) , reporter_(reporter) , namer_(namer) , usingDefaultScrubber_(usingDefaultScrubber) { } Options Options::clone(const Options::FileOptions& fileOptions) const { // TODO error this can retain a previous Options* ??? return Options(fileOptions, scrubber_, reporter_, usingDefaultScrubber_, namer_); } const Reporter& Options::defaultReporter() { static DefaultReporter defaultReporter; return defaultReporter; } Options::Options(Scrubber scrubber) : scrubber_(std::move(scrubber)) { usingDefaultScrubber_ = false; } Options::Options(const Reporter& reporter) : reporter_(reporter) { } Options::FileOptions Options::fileOptions() const { if (fileOptions_.options_ != nullptr) { throw std::logic_error( "Incorrect assumption: A FileOptions has been re-used"); } FileOptions copy = fileOptions_.clone(); copy.options_ = this; return copy; } Scrubber Options::getScrubber() const { return scrubber_; } bool Options::isUsingDefaultScrubber() const { return usingDefaultScrubber_; } std::string Options::scrub(const std::string& input) const { return scrubber_(input); } const Reporter& Options::getReporter() const { return reporter_; } Options Options::withReporter(const Reporter& reporter) const { return Options(fileOptions_, scrubber_, reporter, usingDefaultScrubber_, namer_); } Options Options::withScrubber(Scrubber scrubber) const { return Options(fileOptions_, std::move(scrubber), reporter_, false, namer_); } std::shared_ptr<ApprovalNamer> Options::getNamer() const { return namer_; } Options Options::withNamer(std::shared_ptr<ApprovalNamer> namer) { return Options( fileOptions_, scrubber_, reporter_, usingDefaultScrubber_, std::move(namer)); } } // ******************** From: FrameworkIntegrations.cpp namespace ApprovalTests { void FrameworkIntegrations::setTestPassedNotification( FileApprover::TestPassedNotification notification) { FileApprover::setTestPassedNotification(notification); } void FrameworkIntegrations::setCurrentTest(ApprovalTests::TestName* currentTest) { ApprovalTestNamer::currentTest(currentTest); } } // ******************** From: GoogleConfiguration.cpp namespace ApprovalTests { bool GoogleConfiguration::addTestCaseNameRedundancyCheck( GoogleCustomizationsFactory::Comparator comparator) { return GoogleCustomizationsFactory::addTestCaseNameRedundancyCheck(comparator); } bool GoogleConfiguration::addIgnorableTestCaseNameSuffix(std::string suffix) { return addTestCaseNameRedundancyCheck( createIgnorableTestCaseNameSuffixCheck(suffix)); } GoogleCustomizationsFactory::Comparator GoogleConfiguration::createIgnorableTestCaseNameSuffixCheck(const std::string& suffix) { return [suffix](std::string testFileNameWithExtension, std::string testCaseName) { if (testCaseName.length() <= suffix.length() || !StringUtils::endsWith(testCaseName, suffix)) { return false; } auto withoutSuffix = testCaseName.substr(0, testCaseName.length() - suffix.length()); auto withFileExtension = withoutSuffix + "."; return StringUtils::contains(testFileNameWithExtension, withFileExtension); }; } } // ******************** From: GoogleCustomizationsFactory.cpp namespace ApprovalTests { GoogleCustomizationsFactory::ComparatorContainer& GoogleCustomizationsFactory::comparatorContainer() { static ComparatorContainer container; if (container.empty()) { auto exactNameMatching = [](const std::string& testFileNameWithExtension, const std::string& testCaseName) { return StringUtils::contains(testFileNameWithExtension, testCaseName + "."); }; container.push_back(exactNameMatching); } return container; } GoogleCustomizationsFactory::ComparatorContainer GoogleCustomizationsFactory::getEquivalencyChecks() { return comparatorContainer(); } bool GoogleCustomizationsFactory::addTestCaseNameRedundancyCheck( const GoogleCustomizationsFactory::Comparator& comparator) { comparatorContainer().push_back(comparator); return true; } } // ******************** From: SystemLauncher.cpp namespace ApprovalTests { SystemLauncher::SystemLauncher(bool isForeground, bool allowNonZeroExitCodes) : isForeground_(isForeground), allowNonZeroExitCodes_(allowNonZeroExitCodes) { } bool SystemLauncher::launch(const std::string& commandLine) { std::string launch = getCommandLine(commandLine); SystemUtils::runSystemCommandOrThrow(launch, allowNonZeroExitCodes_); return true; } void SystemLauncher::invokeForWindows(bool useWindows) { useWindows_ = useWindows; } void SystemLauncher::setForeground(bool foreground) { isForeground_ = foreground; } void SystemLauncher::setAllowNonZeroExitCodes(bool allow) { allowNonZeroExitCodes_ = allow; } bool SystemLauncher::isForeground() const { return isForeground_; } std::string SystemLauncher::getCommandLine(const std::string& commandLine) const { std::string launch = useWindows_ ? getWindowsCommandLine(commandLine, isForeground_) : getUnixCommandLine(commandLine, isForeground_); return launch; } std::string SystemLauncher::getWindowsCommandLine(const std::string& commandLine, bool foreground) const { std::string launch = foreground ? (std::string("cmd /S /C ") + "\"" + commandLine + "\"") : ("start \"\" " + commandLine); return launch; } std::string SystemLauncher::getUnixCommandLine(const std::string& commandLine, bool foreground) const { std::string launch = foreground ? commandLine : (commandLine + " &"); return launch; } } // ******************** From: ApprovalTestNamer.cpp #include <iostream> namespace ApprovalTests { std::string TestName::directoryPrefix; bool TestName::checkBuildConfig_ = true; const std::string& TestName::getFileName() const { checkBuildConfiguration(fileName); return fileName; } std::string TestName::getOriginalFileName() { return originalFileName; } void TestName::setFileName(const std::string& file) { originalFileName = file; fileName = file.empty() ? handleBoostQuirks() : findFileName(file); } std::string TestName::findFileName(const std::string& file) { auto newFileName = checkParentDirectoriesForFile(file); return SystemUtils::checkFilenameCase(newFileName); } std::string& TestName::rootDirectoryStorage() { // This method is needed to fix static initialisation order static std::string rootDirectory; return rootDirectory; } std::string TestName::checkParentDirectoriesForFile(const std::string& file) { auto newFileName = directoryPrefix + file; if (!FileUtils::fileExists(newFileName)) { // If the build system is Ninja, try looking several levels higher... std::string backOne = ".." + SystemUtils::getDirectorySeparator(); std::string prefix; for (int i = 0; i != 10; i++) { prefix += backOne; auto candidateName = prefix + file; if (FileUtils::fileExists(candidateName)) { directoryPrefix = prefix; return candidateName; } } } return newFileName; } bool TestName::registerRootDirectoryFromMainFile(const std::string& file) { std::cout << "TestName::registerRootDirectoryFromMainFile from __FILE__ " << file << '\n'; if (file.empty()) { throw std::runtime_error("Cannot register an empty path as root directory"); } std::string adjustedPath = file; std::cout << "TestName::registerRootDirectoryFromMainFile found parent " << adjustedPath << '\n'; rootDirectoryStorage() = FileUtils::getDirectory(adjustedPath); std::cout << "TestName::registerRootDirectoryFromMainFile result " << rootDirectoryStorage() << '\n'; return true; } std::string TestName::getRootDirectory() { if (!rootDirectoryStorage().empty()) { return rootDirectoryStorage(); } else { throw std::runtime_error(HelpMessages::getUnconfiguredRootDirectory()); } } std::string TestName::handleBoostQuirks() const { return ""; } void TestName::checkBuildConfiguration(const std::string& fileName) { if (checkBuildConfig_ && !FileUtils::fileExists(fileName)) { throw std::runtime_error(getMisconfiguredBuildHelp(fileName)); } } std::string TestName::getMisconfiguredBuildHelp(const std::string& fileName) { return "\n\n" + HelpMessages::getMisconfiguredBuildHelp(fileName) + "\n\n"; } std::string ApprovalTestNamer::getTestName() const { std::stringstream ext; auto test = getCurrentTest(); for (size_t i = 0; i < test.sections.size(); i++) { if (0 < i) { ext << "."; } ext << test.sections[i]; } return convertToFileName(ext.str()); } std::string ApprovalTestNamer::convertToFileName(const std::string& fileName) { return FileNameSanitizerFactory::currentSanitizer(fileName); } TestName& ApprovalTestNamer::getCurrentTest() { try { return currentTest(); } catch (const std::runtime_error&) { std::string helpMessage = getMisconfiguredMainHelp(); throw std::runtime_error(helpMessage); } } std::string ApprovalTestNamer::getMisconfiguredMainHelp() { return "\n\n" + HelpMessages::getMisconfiguredMainHelp() + "\n\n"; } std::string ApprovalTestNamer::getFileName() const { return getSourceFileName(); } std::string ApprovalTestNamer::getSourceFileName() const { auto file = getCurrentTest().getFileName(); auto start = file.rfind(SystemUtils::getDirectorySeparator()) + 1; auto end = file.rfind('.'); auto fileName = file.substr(start, end - start); return convertToFileName(fileName); } std::string ApprovalTestNamer::getTestSourceDirectory() const { auto file = getCurrentTest().getFileName(); return FileUtils::getDirectory(file); } std::string ApprovalTestNamer::getRelativeTestSourceDirectory() const { // We are using the original directory - as obtained from __FILE__, // as this seems to be consistent for relative paths, regardless of // Ninja __FILE__ quirks auto originalDir = FileUtils::getDirectory(getCurrentTest().getOriginalFileName()); originalDir = StringUtils::replaceAll(originalDir, TestName::getRootDirectory(), ""); return originalDir; } std::string ApprovalTestNamer::getApprovalsSubdirectory() const { std::string sub_directory; if (!testConfiguration().subdirectory.empty()) { sub_directory = testConfiguration().subdirectory + SystemUtils::getDirectorySeparator(); } return sub_directory; } std::string ApprovalTestNamer::getDirectory() const { std::string directory = getTestSourceDirectory(); std::string sub_directory = getApprovalsSubdirectory(); directory += sub_directory; SystemUtils::ensureDirectoryExists(directory); return directory; } TestName& ApprovalTestNamer::currentTest(TestName* value) { static TestName* staticValue; if (value != nullptr) { staticValue = value; } if (staticValue == nullptr) { throw std::runtime_error("The variable in currentTest() is not initialised"); } return *staticValue; } TestConfiguration& ApprovalTestNamer::testConfiguration() { static TestConfiguration configuration; return configuration; } std::string ApprovalTestNamer::getApprovedFile(std::string extensionWithDot) const { return getFullFileName(".approved", extensionWithDot); } std::string ApprovalTestNamer::getReceivedFile(std::string extensionWithDot) const { return getFullFileName(".received", extensionWithDot); } std::string ApprovalTestNamer::getOutputFileBaseName() const { return getSourceFileName() + "." + getTestName(); } std::string ApprovalTestNamer::getFullFileName(const std::string& approved, const std::string& extensionWithDot) const { std::stringstream ext; ext << getDirectory() << getOutputFileBaseName() << approved << extensionWithDot; return ext.str(); } bool ApprovalTestNamer::setCheckBuildConfig(bool enabled) { auto previous = TestName::checkBuildConfig_; TestName::checkBuildConfig_ = enabled; return previous; } } // ******************** From: DefaultNamerDisposer.cpp namespace ApprovalTests { DefaultNamerDisposer::DefaultNamerDisposer(NamerCreator namerCreator) { previous_result = DefaultNamerFactory::getDefaultNamer(); DefaultNamerFactory::setDefaultNamer(std::move(namerCreator)); } DefaultNamerDisposer::~DefaultNamerDisposer() { DefaultNamerFactory::setDefaultNamer(previous_result); } } // ******************** From: DefaultNamerFactory.cpp namespace ApprovalTests { NamerCreator& DefaultNamerFactory::defaultNamer() { static NamerCreator namer = []() { return std::make_shared<ApprovalTestNamer>(); }; return namer; } NamerCreator DefaultNamerFactory::getDefaultNamer() { return defaultNamer(); } void DefaultNamerFactory::setDefaultNamer(NamerCreator namer) { defaultNamer() = std::move(namer); } } // ******************** From: ExistingFileNamer.cpp namespace ApprovalTests { ExistingFileNamer::ExistingFileNamer(std::string filePath_, const Options& options) : filePath(std::move(filePath_)), options_(options) { } ExistingFileNamer::ExistingFileNamer(const ExistingFileNamer& x) : filePath(x.filePath), options_(x.options_) { } ExistingFileNamer::ExistingFileNamer(ExistingFileNamer&& x) noexcept : filePath(std::move(x.filePath)), options_(x.options_) { } std::string ExistingFileNamer::getApprovedFile(std::string extensionWithDot) const { return options_.getNamer()->getApprovedFile(extensionWithDot); } std::string ExistingFileNamer::getReceivedFile(std::string) const { return filePath; } } // ******************** From: FileNameSanitizerDisposer.cpp #include <sstream> namespace ApprovalTests { FileNameSanitizerDisposer::FileNameSanitizerDisposer(FileNameSanitizer sanitizer) { previous_result = std::move(FileNameSanitizerFactory::currentSanitizer); FileNameSanitizerFactory::currentSanitizer = std::move(sanitizer); } FileNameSanitizerDisposer::~FileNameSanitizerDisposer() { FileNameSanitizerFactory::currentSanitizer = std::move(previous_result); } } // ******************** From: FileNameSanitizerFactory.cpp #include <sstream> namespace ApprovalTests { bool FileNameSanitizerFactory::isForbidden(char c) { static std::string forbiddenChars("\\/:?\"<>|' "); return std::string::npos != forbiddenChars.find(c); } std::string FileNameSanitizerFactory::defaultSanitizer(std::string fileName) { std::stringstream result; for (auto ch : fileName) { if (!isForbidden(ch)) { result << ch; } else { result << "_"; } } return result.str(); } FileNameSanitizer FileNameSanitizerFactory::currentSanitizer = FileNameSanitizerFactory::defaultSanitizer; } // ******************** From: HelpMessages.cpp #include <sstream> namespace ApprovalTests { std::string HelpMessages::getMisconfiguredBuildHelp(const std::string& fileName) { std::string helpMessage = R"(* Welcome to Approval Tests. * * There seems to be a problem with your build configuration. * We cannot find the test source file at: * [fileName] * * For details on how to fix this, please visit: * https://github.com/approvals/ApprovalTests.cpp/blob/master/doc/TroubleshootingMisconfiguredBuild.md * * For advanced users only: * If you believe you have reached this message in error, you can bypass * the check by calling ApprovalTestNamer::setCheckBuildConfig(false); )"; return StringUtils::replaceAll( topAndTailHelpMessage(helpMessage), "[fileName]", fileName); } std::string HelpMessages::getMisconfiguredMainHelp() { std::string helpMessage = R"(* Welcome to Approval Tests. * * You have forgotten to configure your test framework for Approval Tests. * * To do this in Catch, add the following to your main.cpp: * * #define APPROVALS_CATCH * #include "ApprovalTests.hpp" * * To do this in Google Test, add the following to your main.cpp: * * #define APPROVALS_GOOGLETEST * #include "ApprovalTests.hpp" * * To do this in doctest, add the following to your main.cpp: * * #define APPROVALS_DOCTEST * #include "ApprovalTests.hpp" * * To do this in Boost.Test, add the following to your main.cpp: * * #define APPROVALS_BOOSTTEST * #include "ApprovalTests.hpp" * * To do this in CppUTest, add the following to your main.cpp: * * #define APPROVALS_CPPUTEST * #include "ApprovalTests.hpp" * * To do this in [Boost].UT, add the following to your main.cpp: * * #define APPROVALS_UT * #include "ApprovalTests.hpp" * * For more information, please visit: * https://github.com/approvals/ApprovalTests.cpp/blob/master/doc/TroubleshootingMisconfiguredMain.md )"; return topAndTailHelpMessage(helpMessage); } std::string HelpMessages::getUnconfiguredRootDirectory() { std::string helpMessage = R"(* Hello from Approval Tests. * * It looks like your program is calling some code that requires knowledge of * the location of the main() in your source tree. * * To do this, add the following to your main.cpp file: * * APPROVAL_TESTS_REGISTER_MAIN_DIRECTORY * * Currently, this is only required if you are using TemplatedCustomNamer's * {RelativeTestSourceDirectory}. )"; return topAndTailHelpMessage(helpMessage); } std::string HelpMessages::getUnknownEnvVarReporterHelp(const std::string& envVarName, const std::string& selected, const std::vector<std::string>& knowns) { std::string helpMessage = R"(* The environment variable [envVarName] contains the value * [selected] * * This reporter is not recognised. * * Please unset the environment value, or change it to refer to one of the * known reporters: * [known]* * For more information, see: * https://github.com/approvals/ApprovalTests.cpp/blob/master/doc/how_tos/SelectReporterWithEnvironmentVariable.md )"; return envVarErrorMessage(envVarName, selected, knowns, helpMessage); } std::string HelpMessages::getInvalidEnvVarReporterHelp(const std::string& envVarName, const std::string& selected, const std::vector<std::string>& knowns) { std::string helpMessage = R"(* The environment variable [envVarName] contains the value * [selected] * * This reporter is recognised, but cannot be found on this machine. * * Please unset the environment value, or change it to refer to a working * reporter: * [known]* * For more information, see: * https://github.com/approvals/ApprovalTests.cpp/blob/master/doc/how_tos/SelectReporterWithEnvironmentVariable.md )"; return envVarErrorMessage(envVarName, selected, knowns, helpMessage); } std::string HelpMessages::envVarErrorMessage(const std::string& envVarName, const std::string& selected, const std::vector<std::string>& knowns, std::string& helpMessage) { std::stringstream ss; for (auto& known : knowns) { ss << "* " << known << '\n'; } helpMessage = StringUtils::replaceAll(helpMessage, "[selected]", selected); helpMessage = StringUtils::replaceAll(helpMessage, "[envVarName]", envVarName); return topAndTailHelpMessage( StringUtils::replaceAll(helpMessage, "[known]", ss.str())); } std::string HelpMessages::topAndTailHelpMessage(const std::string& message) { const std::string lineBreak = "**************************************************************" "***************"; const std::string lineBuffer = "* " " *\n"; return lineBreak + '\n' + lineBuffer + message + lineBuffer + lineBreak; } } // ******************** From: NamerFactory.cpp namespace ApprovalTests { SectionNameDisposer NamerFactory::appendToOutputFilename(const std::string& sectionName) { return SectionNameDisposer(ApprovalTestNamer::currentTest(), sectionName); } } // ******************** From: SectionNameDisposer.cpp namespace ApprovalTests { SectionNameDisposer::SectionNameDisposer(TestName& currentTest_, const std::string& scope_name) : currentTest(currentTest_) { // Add extra section to output filename, to allow multiple files // to verified from a single test: currentTest_.sections.push_back(scope_name); } SectionNameDisposer::~SectionNameDisposer() { // Remove the extra section we added in the constructor currentTest.sections.pop_back(); } } // ******************** From: SeparateApprovedAndReceivedDirectoriesNamer.cpp namespace ApprovalTests { std::string separateDirectoryPath() { // clang-format off auto path = "{TestSourceDirectory}/{ApprovalsSubdirectory}/{ApprovedOrReceived}/{TestFileName}.{TestCaseName}.{FileExtension}"; // clang-format on return path; } SeparateApprovedAndReceivedDirectoriesNamer:: SeparateApprovedAndReceivedDirectoriesNamer() : TemplatedCustomNamer(separateDirectoryPath()) { } DefaultNamerDisposer SeparateApprovedAndReceivedDirectoriesNamer::useAsDefaultNamer() { return Approvals::useAsDefaultNamer([]() { return std::make_shared<SeparateApprovedAndReceivedDirectoriesNamer>(); }); } } // ******************** From: SubdirectoryDisposer.cpp namespace ApprovalTests { SubdirectoryDisposer::SubdirectoryDisposer(std::string subdirectory) { previous_result = ApprovalTestNamer::testConfiguration().subdirectory; ApprovalTestNamer::testConfiguration().subdirectory = std::move(subdirectory); } SubdirectoryDisposer::~SubdirectoryDisposer() { ApprovalTestNamer::testConfiguration().subdirectory = previous_result; } } // ******************** From: TemplatedCustomNamer.cpp #include <functional> #include <utility> namespace { std::string replaceIfContains(std::string input, std::string pattern, std::function<std::string(void)> replacer) { if (!ApprovalTests::StringUtils::contains(input, pattern)) { return input; } return ApprovalTests::StringUtils::replaceAll(input, pattern, replacer()); } } namespace ApprovalTests { TemplatedCustomNamer::TemplatedCustomNamer(std::string templateString) : template_(std::move(templateString)) { if (!StringUtils::contains(template_, "{ApprovedOrReceived}")) { throw std::runtime_error( "Template must contain `{ApprovedOrReceived}` or the received and " "approved files will not be unique.\n" "Template: " + template_); } } Path TemplatedCustomNamer::constructFromTemplate( const std::string& extensionWithDot, const std::string& approvedOrReceivedReplacement) const { std::string result = template_; auto testSourceDirectory = "{TestSourceDirectory}"; auto relativeTestSourceDirectory = "{RelativeTestSourceDirectory}"; auto approvalsSubdirectory = "{ApprovalsSubdirectory}"; auto testFileName = "{TestFileName}"; auto testCaseName = "{TestCaseName}"; auto approvedOrReceived = "{ApprovedOrReceived}"; auto fileExtension = "{FileExtension}"; using namespace ApprovalTests; // clang-format off result = replaceIfContains(result, fileExtension, [&](){return extensionWithDot.substr(1);}); result = replaceIfContains(result, approvalsSubdirectory, [&](){return namer_.getApprovalsSubdirectory();}); result = replaceIfContains(result, relativeTestSourceDirectory, [&](){return namer_.getRelativeTestSourceDirectory();}); result = replaceIfContains(result, testFileName, [&](){return namer_.getSourceFileName();}); result = replaceIfContains(result, testCaseName, [&](){return namer_.getTestName();}); result = replaceIfContains(result, testSourceDirectory, [&](){return namer_.getTestSourceDirectory();}); result = replaceIfContains(result, approvedOrReceived, [&](){return approvedOrReceivedReplacement;}); // clang-format on // Convert to native directory separators: return Path(result); } std::string TemplatedCustomNamer::getApprovedFile(std::string extensionWithDot) const { return getApprovedFileAsPath(extensionWithDot).toString(); } std::string TemplatedCustomNamer::getReceivedFile(std::string extensionWithDot) const { return getReceivedFileAsPath(extensionWithDot).toString(); } Path TemplatedCustomNamer::getApprovedFileAsPath(std::string extensionWithDot) const { return constructFromTemplate(extensionWithDot, "approved"); } Path TemplatedCustomNamer::getReceivedFileAsPath(std::string extensionWithDot) const { return constructFromTemplate(extensionWithDot, "received"); } std::shared_ptr<TemplatedCustomNamer> TemplatedCustomNamer::create(std::string templateString) { return std::make_shared<TemplatedCustomNamer>(templateString); } DefaultNamerDisposer TemplatedCustomNamer::useAsDefaultNamer(std::string templateString) { return Approvals::useAsDefaultNamer([=]() { return create(templateString); }); } } // ******************** From: AutoApproveIfMissingReporter.cpp namespace ApprovalTests { bool AutoApproveIfMissingReporter::report(std::string received, std::string approved) const { if (FileUtils::fileExists(approved)) { return false; } return AutoApproveReporter().report(received, approved); } } // ******************** From: AutoApproveReporter.cpp #include <iostream> namespace ApprovalTests { bool AutoApproveReporter::report(std::string received, std::string approved) const { std::cout << "file " << approved << " automatically approved - next run should succeed\n"; FileUtilsSystemSpecific::copyFile(received, approved); return true; } } // ******************** From: BlockingReporter.cpp namespace ApprovalTests { BlockingReporter::BlockingReporter(std::shared_ptr<Blocker> blocker_) : blocker(std::move(blocker_)) { } std::shared_ptr<BlockingReporter> BlockingReporter::onMachineNamed(const std::string& machineName) { auto machineBlocker = std::make_shared<MachineBlocker>(MachineBlocker::onMachineNamed(machineName)); return std::make_shared<BlockingReporter>(machineBlocker); } std::shared_ptr<BlockingReporter> BlockingReporter::onMachinesNotNamed(const std::string& machineName) { auto machineBlocker = std::make_shared<MachineBlocker>( MachineBlocker::onMachinesNotNamed(machineName)); return std::make_shared<BlockingReporter>(machineBlocker); } bool BlockingReporter::report(std::string, std::string) const { return blocker->isBlockingOnThisMachine(); } } // ******************** From: CIBuildOnlyReporter.cpp namespace ApprovalTests { CIBuildOnlyReporter::CIBuildOnlyReporter(std::shared_ptr<Reporter> reporter) : m_reporter(reporter) { } bool CIBuildOnlyReporter::report(std::string received, std::string approved) const { if (!isRunningUnderCI()) { return false; } m_reporter->report(received, approved); // Return true regardless of whether our report succeeded or not, // so that no later reporters run. return true; } bool CIBuildOnlyReporter::isRunningUnderCI() { /* auto AppVeyor = {"CI", "APPVEYOR"}; // https://www.appveyor.com/docs/environment-variables/ auto AzurePipelines = {"TF_BUILD"}; // https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&viewFallbackFrom=vsts&tabs=yaml auto GitHubActions = {"GITHUB_ACTIONS"}; // https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables auto GoCD = {"GO_SERVER_URL"}: // https://docs.gocd.org/current/faq/dev_use_current_revision_in_build.html auto Jenkins = {"JENKINS_URL"}: // https://wiki.jenkins.io/display/JENKINS/Building+a+software+project auto TeamCity = {"TEAMCITY_VERSION"}; // https://confluence.jetbrains.com/display/TCD18/Predefined+Build+Parameters auto Travis = {"CI", "TRAVIS", "CONTINUOUS_INTEGRATION"}; // https://docs.travis-ci.com/user/environment-variables/#default-environment-variables auto environmentVariablesForCI = combine({ AppVeyor, AzurePipelines, GitHubActions, GoCD Jenkins, TeamCity, Travis, }); */ auto environmentVariablesForCI = { "CI", "CONTINUOUS_INTEGRATION", "GITHUB_ACTIONS", "GO_SERVER_URL", "JENKINS_URL", "TEAMCITY_VERSION", "TF_BUILD" }; for (const auto& variable : environmentVariablesForCI) { if (!SystemUtils::safeGetEnv(variable).empty()) { return true; } } return false; } } // ******************** From: CIBuildOnlyReporterUtils.cpp namespace ApprovalTests { FrontLoadedReporterDisposer CIBuildOnlyReporterUtils::useAsFrontLoadedReporter( const std::shared_ptr<Reporter>& reporter) { return Approvals::useAsFrontLoadedReporter( std::make_shared<ApprovalTests::CIBuildOnlyReporter>(reporter)); } } // ******************** From: ClipboardReporter.cpp namespace ApprovalTests { std::string ClipboardReporter::getCommandLineFor(const std::string& received, const std::string& approved, bool isWindows) { if (isWindows) { return std::string("move /Y ") + "\"" + received + "\" \"" + approved + "\""; } else { return std::string("mv ") + "\"" + received + "\" \"" + approved + "\""; } } bool ClipboardReporter::report(std::string received, std::string approved) const { copyToClipboard( getCommandLineFor(received, approved, SystemUtils::isWindowsOs())); return true; } void ClipboardReporter::copyToClipboard(const std::string& newClipboard) { /* This will probably not work on Linux. From https://stackoverflow.com/a/750466/104370 In case of X, yes, there's xclip (and others). xclip -selection c will send data to the clipboard that works with Ctrl-C, Ctrl-V in most applications. If you're trying to talk to the Mac OS X clipboard, there's pbcopy. If you're in Linux terminal mode (no X) then maybe you need to look into gpm. There's also GNU screen which has a clipboard. To put stuff in there, look at the screen command "readreg". Under Windows/cygwin, use /dev/clipboard or clip for newer versions of Windows (at least Windows 10). */ std::string clipboardCommand; if (SystemUtils::isWindowsOs()) { clipboardCommand = "clip"; } else if (SystemUtils::isMacOs()) { clipboardCommand = "pbcopy"; } else { clipboardCommand = "pbclip"; } auto cmd = std::string("echo ") + newClipboard + " | " + clipboardCommand; SystemUtils::runSystemCommandOrThrow(cmd); } } // ******************** From: CombinationReporter.cpp namespace ApprovalTests { CombinationReporter::CombinationReporter(const std::vector<Reporter*>& theReporters) { for (auto r : theReporters) { reporters.push_back(std::unique_ptr<Reporter>(r)); } } bool CombinationReporter::report(std::string received, std::string approved) const { bool result = false; for (auto& r : reporters) { result |= r->report(received, approved); } return result; } } // ******************** From: CommandReporter.cpp namespace ApprovalTests { std::string CommandReporter::assembleFullCommand(const std::string& received, const std::string& approved) const { auto convertedCommand = '"' + converter->convertProgramForCygwin(cmd) + '"'; auto convertedReceived = '"' + converter->convertFileArgumentForCygwin(received) + '"'; auto convertedApproved = '"' + converter->convertFileArgumentForCygwin(approved) + '"'; std::string args; args = StringUtils::replaceAll( arguments, DiffInfo::receivedFileTemplate(), convertedReceived); args = StringUtils::replaceAll( args, DiffInfo::approvedFileTemplate(), convertedApproved); return convertedCommand + ' ' + args; } CommandReporter::CommandReporter(std::string command, CommandLauncher* launcher) : cmd(std::move(command)), l(launcher) { checkForCygwin(); } CommandReporter::CommandReporter(std::string command, std::string args, CommandLauncher* launcher) : cmd(std::move(command)), arguments(std::move(args)), l(launcher) { checkForCygwin(); } bool CommandReporter::exists(const std::string& command) { bool foundByWhich = false; if (!SystemUtils::isWindowsOs()) { std::string which = "which " + command + " > /dev/null 2>&1"; int result = system(which.c_str()); foundByWhich = (result == 0); } return foundByWhich || FileUtils::fileExists(command); } bool CommandReporter::report(std::string received, std::string approved) const { if (!exists(cmd)) { return false; } FileUtils::ensureFileExists(approved); return l->launch(assembleFullCommand(received, approved)); } std::string CommandReporter::getCommandLine(const std::string& received, const std::string& approved) const { return l->getCommandLine(assembleFullCommand(received, approved)); } void CommandReporter::checkForCygwin() { useCygwinConversions(SystemUtils::isCygwin()); } void CommandReporter::useCygwinConversions(bool useCygwin) { if (useCygwin) { converter = std::make_shared<ConvertForCygwin>(); } else { converter = std::make_shared<DoNothing>(); } } } // ******************** From: ConvertForCygwin.cpp namespace ApprovalTests { std::string ConvertForCygwin::convertProgramForCygwin(const std::string& filePath) { return "$(cygpath '" + filePath + "')"; } std::string ConvertForCygwin::convertFileArgumentForCygwin(const std::string& filePath) { return "$(cygpath -aw '" + filePath + "')"; } std::string DoNothing::convertProgramForCygwin(const std::string& filePath) { return filePath; } std::string DoNothing::convertFileArgumentForCygwin(const std::string& filePath) { return filePath; } } // ******************** From: CustomReporter.cpp namespace ApprovalTests { std::shared_ptr<GenericDiffReporter> CustomReporter::create(std::string path, Type type) { return create(std::move(path), DiffInfo::getDefaultArguments(), type); } std::shared_ptr<GenericDiffReporter> CustomReporter::create(std::string path, std::string arguments, Type type) { DiffInfo info(std::move(path), std::move(arguments), type); return std::make_shared<GenericDiffReporter>(info); } std::shared_ptr<GenericDiffReporter> CustomReporter::createForegroundReporter( std::string path, Type type, bool allowNonZeroExitCodes) { return createForegroundReporter(std::move(path), DiffInfo::getDefaultArguments(), type, allowNonZeroExitCodes); } std::shared_ptr<GenericDiffReporter> CustomReporter::createForegroundReporter( std::string path, std::string arguments, Type type, bool allowNonZeroExitCodes) { DiffInfo info(std::move(path), std::move(arguments), type); auto reporter = std::make_shared<GenericDiffReporter>(info); reporter->launcher.setForeground(true); reporter->launcher.setAllowNonZeroExitCodes(allowNonZeroExitCodes); return reporter; } } // ******************** From: DefaultFrontLoadedReporter.cpp namespace ApprovalTests { DefaultFrontLoadedReporter::DefaultFrontLoadedReporter() : FirstWorkingReporter({new CIBuildOnlyReporter()}) { } } // ******************** From: DefaultReporter.cpp namespace ApprovalTests { bool DefaultReporter::report(std::string received, std::string approved) const { return DefaultReporterFactory::getDefaultReporter()->report(received, approved); } } // ******************** From: DefaultReporterDisposer.cpp namespace ApprovalTests { DefaultReporterDisposer::DefaultReporterDisposer( const std::shared_ptr<Reporter>& reporter) { previous_result = DefaultReporterFactory::getDefaultReporter(); DefaultReporterFactory::setDefaultReporter(reporter); } DefaultReporterDisposer::~DefaultReporterDisposer() { DefaultReporterFactory::setDefaultReporter(previous_result); } } // ******************** From: DefaultReporterFactory.cpp namespace ApprovalTests { std::shared_ptr<Reporter>& DefaultReporterFactory::defaultReporter() { static std::shared_ptr<Reporter> reporter = std::make_shared<DiffReporter>(); return reporter; } std::shared_ptr<Reporter> DefaultReporterFactory::getDefaultReporter() { return defaultReporter(); } void DefaultReporterFactory::setDefaultReporter(const std::shared_ptr<Reporter>& reporter) { defaultReporter() = reporter; } } // ******************** From: DiffInfo.cpp namespace ApprovalTests { std::string DiffInfo::receivedFileTemplate() { return "{Received}"; } std::string DiffInfo::approvedFileTemplate() { return "{Approved}"; } std::string DiffInfo::programFileTemplate() { return "{ProgramFiles}"; } std::string DiffInfo::getDefaultArguments() { return receivedFileTemplate() + ' ' + approvedFileTemplate(); } DiffInfo::DiffInfo(std::string program_, Type type_) : program(std::move(program_)), arguments(getDefaultArguments()), type(type_) { } DiffInfo::DiffInfo(std::string program_, std::string arguments_, Type type_) : program(std::move(program_)), arguments(std::move(arguments_)), type(type_) { } std::vector<std::string> DiffInfo::getProgramFileLocations() { std::vector<std::string> possibleWindowsPaths; const std::vector<const char*> envVars = { "ProgramFiles", "ProgramW6432", "ProgramFiles(x86)"}; for (const auto& envVar : envVars) { std::string envVarValue = SystemUtils::safeGetEnv(envVar); if (!envVarValue.empty()) { envVarValue += '\\'; possibleWindowsPaths.push_back(envVarValue); } } return possibleWindowsPaths; } std::string DiffInfo::getProgramForOs() const { std::string result = program; if (result.rfind(programFileTemplate(), 0) == 0) { std::vector<std::string> possibleWindowsPaths = getProgramFileLocations(); for (const auto& path : possibleWindowsPaths) { auto result1 = StringUtils::replaceAll(result, programFileTemplate(), path); if (FileUtils::fileExists(result1)) { return result1; } } } return result; } } // ******************** From: DiffPrograms.cpp /////////////////////////////////////////////////////////////////////////////// #define APPROVAL_TESTS_MACROS_ENTRY(name, defaultValue) \ DiffInfo name() \ { \ return defaultValue; \ } /////////////////////////////////////////////////////////////////////////////// namespace ApprovalTests { namespace DiffPrograms { namespace Mac { APPROVAL_TESTS_MACROS_ENTRY( DIFF_MERGE, DiffInfo("/Applications/DiffMerge.app/Contents/MacOS/DiffMerge", "{Received} {Approved} -nosplash", Type::TEXT)) APPROVAL_TESTS_MACROS_ENTRY( ARAXIS_MERGE, DiffInfo("/Applications/Araxis Merge.app/Contents/Utilities/compare", Type::TEXT_AND_IMAGE)) APPROVAL_TESTS_MACROS_ENTRY( BEYOND_COMPARE, DiffInfo("/Applications/Beyond Compare.app/Contents/MacOS/bcomp", Type::TEXT)) APPROVAL_TESTS_MACROS_ENTRY( KALEIDOSCOPE, DiffInfo("/Applications/Kaleidoscope.app/Contents/MacOS/ksdiff", Type::TEXT_AND_IMAGE)) APPROVAL_TESTS_MACROS_ENTRY( SUBLIME_MERGE, DiffInfo("/Applications/Sublime " "Merge.app/Contents/SharedSupport/bin/smerge", "mergetool --no-wait {Received} {Approved} -o {Approved}", Type::TEXT)) APPROVAL_TESTS_MACROS_ENTRY( KDIFF3, DiffInfo("/Applications/kdiff3.app/Contents/MacOS/kdiff3", "{Received} {Approved} -m -o {Approved}", Type::TEXT)) APPROVAL_TESTS_MACROS_ENTRY( P4MERGE, DiffInfo("/Applications/p4merge.app/Contents/MacOS/p4merge", Type::TEXT_AND_IMAGE)) APPROVAL_TESTS_MACROS_ENTRY( TK_DIFF, DiffInfo("/Applications/TkDiff.app/Contents/MacOS/tkdiff", Type::TEXT)) APPROVAL_TESTS_MACROS_ENTRY( VS_CODE, DiffInfo("/Applications/Visual Studio " "Code.app/Contents/Resources/app/bin/code", "-d {Received} {Approved}", Type::TEXT)) APPROVAL_TESTS_MACROS_ENTRY(CLION, DiffInfo("clion", "nosplash diff {Received} {Approved}", Type::TEXT)) } namespace Linux { APPROVAL_TESTS_MACROS_ENTRY( SUBLIME_MERGE_SNAP, DiffInfo("/snap/bin/sublime-merge", "mergetool --no-wait {Received} {Approved} -o {Approved}", Type::TEXT)) APPROVAL_TESTS_MACROS_ENTRY( SUBLIME_MERGE_FLATPAK, DiffInfo("/var/lib/flatpak/exports/bin/com.sublimemerge.App", "mergetool --no-wait {Received} {Approved} -o {Approved}", Type::TEXT)) APPROVAL_TESTS_MACROS_ENTRY( SUBLIME_MERGE_REPOSITORY_PACKAGE, DiffInfo("smerge", "mergetool --no-wait {Received} {Approved} -o {Approved}", Type::TEXT)) APPROVAL_TESTS_MACROS_ENTRY( SUBLIME_MERGE_DIRECT_DOWNLOAD, DiffInfo("/opt/sublime_merge/sublime_merge", "mergetool --no-wait {Received} {Approved} -o {Approved}", Type::TEXT)) // More ideas available from: https://www.tecmint.com/best-linux-file-diff-tools-comparison/ APPROVAL_TESTS_MACROS_ENTRY(KDIFF3, DiffInfo("kdiff3", "{Received} {Approved} -m -o {Approved}", Type::TEXT)) APPROVAL_TESTS_MACROS_ENTRY(MELD, DiffInfo("meld", Type::TEXT)) APPROVAL_TESTS_MACROS_ENTRY(BEYOND_COMPARE, DiffInfo("bcompare", Type::TEXT_AND_IMAGE)) } namespace Windows { APPROVAL_TESTS_MACROS_ENTRY( BEYOND_COMPARE_3, DiffInfo("{ProgramFiles}Beyond Compare 3\\BCompare.exe", Type::TEXT_AND_IMAGE)) APPROVAL_TESTS_MACROS_ENTRY( BEYOND_COMPARE_4, DiffInfo("{ProgramFiles}Beyond Compare 4\\BCompare.exe", Type::TEXT_AND_IMAGE)) APPROVAL_TESTS_MACROS_ENTRY( TORTOISE_IMAGE_DIFF, DiffInfo("{ProgramFiles}TortoiseSVN\\bin\\TortoiseIDiff.exe", "/left:{Received} /right:{Approved}", Type::IMAGE)) APPROVAL_TESTS_MACROS_ENTRY( TORTOISE_TEXT_DIFF, DiffInfo("{ProgramFiles}TortoiseSVN\\bin\\TortoiseMerge.exe", Type::TEXT)) APPROVAL_TESTS_MACROS_ENTRY( TORTOISE_GIT_IMAGE_DIFF, DiffInfo("{ProgramFiles}TortoiseGit\\bin\\TortoiseGitIDiff.exe", "/left:{Received} /right:{Approved}", Type::IMAGE)) APPROVAL_TESTS_MACROS_ENTRY( TORTOISE_GIT_TEXT_DIFF, DiffInfo("{ProgramFiles}TortoiseGit\\bin\\TortoiseGitMerge.exe", Type::TEXT)) APPROVAL_TESTS_MACROS_ENTRY(WIN_MERGE_REPORTER, DiffInfo("{ProgramFiles}WinMerge\\WinMergeU.exe", Type::TEXT_AND_IMAGE)) APPROVAL_TESTS_MACROS_ENTRY( ARAXIS_MERGE, DiffInfo("{ProgramFiles}Araxis\\Araxis Merge\\Compare.exe", Type::TEXT_AND_IMAGE)) APPROVAL_TESTS_MACROS_ENTRY( CODE_COMPARE, DiffInfo("{ProgramFiles}Devart\\Code Compare\\CodeCompare.exe", Type::TEXT)) APPROVAL_TESTS_MACROS_ENTRY( SUBLIME_MERGE, DiffInfo("{ProgramFiles}Sublime Merge\\smerge.exe", "mergetool --no-wait {Received} {Approved} -o {Approved}", Type::TEXT)) APPROVAL_TESTS_MACROS_ENTRY(KDIFF3, DiffInfo("{ProgramFiles}KDiff3\\bin\\kdiff3.exe", "{Received} {Approved} -m -o {Approved}", Type::TEXT)) APPROVAL_TESTS_MACROS_ENTRY( VS_CODE, DiffInfo("{ProgramFiles}Microsoft VS Code\\Code.exe", "-d {Received} {Approved}", Type::TEXT)) } } } // ******************** From: DiffReporter.cpp namespace ApprovalTests { DiffReporter::DiffReporter() : FirstWorkingReporter({new EnvironmentVariableReporter(), new Mac::MacDiffReporter(), new Linux::LinuxDiffReporter(), new Windows::WindowsDiffReporter()}) { } } // ******************** From: EnvironmentVariableReporter.cpp namespace ApprovalTests { bool EnvironmentVariableReporter::report(std::string received, std::string approved) const { // Get the env var const auto envVarName = environmentVariableName(); const auto envVar = SystemUtils::safeGetEnv(envVarName.c_str()); return report(envVar, received, approved); } bool EnvironmentVariableReporter::report(const std::string& envVar, const std::string& received, const std::string& approved) const { if (envVar.empty()) { return false; } auto reporter = factory.createReporter(envVar); auto known = factory.allSupportedReporterNames(); if (!reporter) { auto message = HelpMessages::getUnknownEnvVarReporterHelp( EnvironmentVariableReporter::environmentVariableName(), envVar, known); throw std::runtime_error(message); } auto reporter_worked = reporter->report(received, approved); if (!reporter_worked) { auto message = HelpMessages::getInvalidEnvVarReporterHelp( EnvironmentVariableReporter::environmentVariableName(), envVar, known); throw std::runtime_error(message); } return reporter_worked; } std::string EnvironmentVariableReporter::environmentVariableName() { return "APPROVAL_TESTS_USE_REPORTER"; } } // ******************** From: FirstWorkingReporter.cpp namespace ApprovalTests { FirstWorkingReporter::FirstWorkingReporter(const std::vector<Reporter*>& theReporters) { for (auto r : theReporters) { reporters.push_back(std::shared_ptr<Reporter>(r)); } } FirstWorkingReporter::FirstWorkingReporter( const std::vector<std::shared_ptr<Reporter>>& reporters_) { this->reporters = reporters_; } bool FirstWorkingReporter::report(std::string received, std::string approved) const { for (auto& r : reporters) { if (r->report(received, approved)) { return true; } } return false; } } // ******************** From: FrontLoadedReporterDisposer.cpp namespace ApprovalTests { FrontLoadedReporterDisposer::FrontLoadedReporterDisposer( const std::shared_ptr<Reporter>& reporter) { previous_result = FrontLoadedReporterFactory::getFrontLoadedReporter(); FrontLoadedReporterFactory::setFrontLoadedReporter(reporter); } FrontLoadedReporterDisposer::~FrontLoadedReporterDisposer() { FrontLoadedReporterFactory::setFrontLoadedReporter(previous_result); } } // ******************** From: FrontLoadedReporterFactory.cpp namespace ApprovalTests { std::shared_ptr<Reporter>& FrontLoadedReporterFactory::frontLoadedReporter() { static std::shared_ptr<Reporter> reporter = std::make_shared<DefaultFrontLoadedReporter>(); return reporter; } std::shared_ptr<Reporter> FrontLoadedReporterFactory::getFrontLoadedReporter() { return frontLoadedReporter(); } void FrontLoadedReporterFactory::setFrontLoadedReporter( const std::shared_ptr<Reporter>& reporter) { frontLoadedReporter() = reporter; } } // ******************** From: GenericDiffReporter.cpp namespace ApprovalTests { GenericDiffReporter::GenericDiffReporter(const std::string& program) : CommandReporter(program, &launcher) { } GenericDiffReporter::GenericDiffReporter(const DiffInfo& info) : CommandReporter(info.getProgramForOs(), info.arguments, &launcher) { } } // ******************** From: LinuxReporters.cpp namespace ApprovalTests { namespace Linux { SublimeMergeSnapReporter::SublimeMergeSnapReporter() : GenericDiffReporter(DiffPrograms::Linux::SUBLIME_MERGE_SNAP()) { launcher.setForeground(true); } SublimeMergeFlatpakReporter::SublimeMergeFlatpakReporter() : GenericDiffReporter(DiffPrograms::Linux::SUBLIME_MERGE_FLATPAK()) { launcher.setForeground(true); } SublimeMergeRepositoryPackageReporter::SublimeMergeRepositoryPackageReporter() : GenericDiffReporter(DiffPrograms::Linux::SUBLIME_MERGE_REPOSITORY_PACKAGE()) { launcher.setForeground(true); } SublimeMergeDirectDownloadReporter::SublimeMergeDirectDownloadReporter() : GenericDiffReporter(DiffPrograms::Linux::SUBLIME_MERGE_DIRECT_DOWNLOAD()) { launcher.setForeground(true); } SublimeMergeReporter::SublimeMergeReporter() : FirstWorkingReporter({new SublimeMergeSnapReporter(), new SublimeMergeFlatpakReporter(), new SublimeMergeRepositoryPackageReporter(), new SublimeMergeDirectDownloadReporter()}) { } KDiff3Reporter::KDiff3Reporter() : GenericDiffReporter(DiffPrograms::Linux::KDIFF3()) { } MeldReporter::MeldReporter() : GenericDiffReporter(DiffPrograms::Linux::MELD()) { } BeyondCompareReporter::BeyondCompareReporter() : GenericDiffReporter(DiffPrograms::Linux::BEYOND_COMPARE()) { } LinuxDiffReporter::LinuxDiffReporter() : FirstWorkingReporter({ new BeyondCompareReporter(), new MeldReporter(), new SublimeMergeReporter(), new KDiff3Reporter() }) { } } } // ******************** From: MacReporters.cpp namespace ApprovalTests { namespace Mac { DiffMergeReporter::DiffMergeReporter() : GenericDiffReporter(DiffPrograms::Mac::DIFF_MERGE()) { } AraxisMergeReporter::AraxisMergeReporter() : GenericDiffReporter(DiffPrograms::Mac::ARAXIS_MERGE()) { } VisualStudioCodeReporter::VisualStudioCodeReporter() : GenericDiffReporter(DiffPrograms::Mac::VS_CODE()) { } BeyondCompareReporter::BeyondCompareReporter() : GenericDiffReporter(DiffPrograms::Mac::BEYOND_COMPARE()) { } KaleidoscopeReporter::KaleidoscopeReporter() : GenericDiffReporter(DiffPrograms::Mac::KALEIDOSCOPE()) { } SublimeMergeReporter::SublimeMergeReporter() : GenericDiffReporter(DiffPrograms::Mac::SUBLIME_MERGE()) { launcher.setForeground(true); } KDiff3Reporter::KDiff3Reporter() : GenericDiffReporter(DiffPrograms::Mac::KDIFF3()) { } P4MergeReporter::P4MergeReporter() : GenericDiffReporter(DiffPrograms::Mac::P4MERGE()) { } TkDiffReporter::TkDiffReporter() : GenericDiffReporter(DiffPrograms::Mac::TK_DIFF()) { } CLionDiffReporter::CLionDiffReporter() : GenericDiffReporter(DiffPrograms::Mac::CLION()) { } MacDiffReporter::MacDiffReporter() : FirstWorkingReporter({ new AraxisMergeReporter(), new BeyondCompareReporter(), new DiffMergeReporter(), new KaleidoscopeReporter(), new P4MergeReporter(), new SublimeMergeReporter(), new KDiff3Reporter(), new TkDiffReporter(), new VisualStudioCodeReporter(), new CLionDiffReporter() }) { } bool MacDiffReporter::report(std::string received, std::string approved) const { if (!SystemUtils::isMacOs()) { return false; } return FirstWorkingReporter::report(received, approved); } } } // ******************** From: QuietReporter.cpp namespace ApprovalTests { bool QuietReporter::report(std::string, std::string) const { return true; } } // ******************** From: ReporterFactory.cpp #include <map> #include <functional> #define APPROVAL_TESTS_REGISTER_REPORTER(name) \ map[#name] = []() { return std::unique_ptr<Reporter>(new name); } namespace ApprovalTests { std::string getOsPrefix() { if (SystemUtils::isMacOs()) { return "Mac::"; } if (SystemUtils::isWindowsOs()) { return "Windows::"; } return "Linux::"; } ReporterFactory::ReporterFactory() : map(createMap()) { } ReporterFactory::Reporters ReporterFactory::createMap() { Reporters map; APPROVAL_TESTS_REGISTER_REPORTER(AutoApproveIfMissingReporter); APPROVAL_TESTS_REGISTER_REPORTER(AutoApproveReporter); APPROVAL_TESTS_REGISTER_REPORTER(CIBuildOnlyReporter); APPROVAL_TESTS_REGISTER_REPORTER(ClipboardReporter); APPROVAL_TESTS_REGISTER_REPORTER(DefaultFrontLoadedReporter); APPROVAL_TESTS_REGISTER_REPORTER(DefaultReporter); APPROVAL_TESTS_REGISTER_REPORTER(DiffReporter); APPROVAL_TESTS_REGISTER_REPORTER(EnvironmentVariableReporter); APPROVAL_TESTS_REGISTER_REPORTER(QuietReporter); APPROVAL_TESTS_REGISTER_REPORTER(TextDiffReporter); APPROVAL_TESTS_REGISTER_REPORTER(Linux::BeyondCompareReporter); APPROVAL_TESTS_REGISTER_REPORTER(Linux::MeldReporter); APPROVAL_TESTS_REGISTER_REPORTER(Linux::SublimeMergeReporter); APPROVAL_TESTS_REGISTER_REPORTER(Linux::KDiff3Reporter); APPROVAL_TESTS_REGISTER_REPORTER(Mac::AraxisMergeReporter); APPROVAL_TESTS_REGISTER_REPORTER(Mac::BeyondCompareReporter); APPROVAL_TESTS_REGISTER_REPORTER(Mac::DiffMergeReporter); APPROVAL_TESTS_REGISTER_REPORTER(Mac::KaleidoscopeReporter); APPROVAL_TESTS_REGISTER_REPORTER(Mac::P4MergeReporter); APPROVAL_TESTS_REGISTER_REPORTER(Mac::SublimeMergeReporter); APPROVAL_TESTS_REGISTER_REPORTER(Mac::KDiff3Reporter); APPROVAL_TESTS_REGISTER_REPORTER(Mac::TkDiffReporter); APPROVAL_TESTS_REGISTER_REPORTER(Mac::VisualStudioCodeReporter); APPROVAL_TESTS_REGISTER_REPORTER(Mac::CLionDiffReporter); APPROVAL_TESTS_REGISTER_REPORTER(Windows::TortoiseDiffReporter); APPROVAL_TESTS_REGISTER_REPORTER(Windows::TortoiseGitDiffReporter); APPROVAL_TESTS_REGISTER_REPORTER(Windows::BeyondCompareReporter); APPROVAL_TESTS_REGISTER_REPORTER(Windows::WinMergeReporter); APPROVAL_TESTS_REGISTER_REPORTER(Windows::AraxisMergeReporter); APPROVAL_TESTS_REGISTER_REPORTER(Windows::CodeCompareReporter); APPROVAL_TESTS_REGISTER_REPORTER(Windows::SublimeMergeReporter); APPROVAL_TESTS_REGISTER_REPORTER(Windows::KDiff3Reporter); APPROVAL_TESTS_REGISTER_REPORTER(Windows::VisualStudioCodeReporter); return map; } std::unique_ptr<Reporter> ReporterFactory::createReporter(const std::string& reporterName) const { auto osPrefix = getOsPrefix(); auto key = findReporterName(osPrefix, reporterName); if (!key.empty()) { return map.at(key)(); } return std::unique_ptr<Reporter>(); } std::vector<std::string> ReporterFactory::allSupportedReporterNames() const { std::vector<std::string> result; for (auto& p : map) { result.push_back(p.first); } return result; } std::string ReporterFactory::findReporterName(const std::string& osPrefix, const std::string& reporterName) const { auto trimmedReporterName = StringUtils::trim(reporterName); trimmedReporterName = StringUtils::toLower(trimmedReporterName); std::vector<std::string> candidateNames = { trimmedReporterName, // Allow program names to be specified without Reporter suffix trimmedReporterName + "reporter", // Allow names without os namespace StringUtils::toLower(osPrefix) + trimmedReporterName, StringUtils::toLower(osPrefix) + trimmedReporterName + "reporter", }; for (auto& candidateName : candidateNames) { auto iter = std::find_if( map.begin(), map.end(), [&](const Reporters::value_type pair) { return StringUtils::toLower(pair.first) == candidateName; }); if (iter != map.end()) { return iter->first; } } return std::string{}; } } // ******************** From: TextDiffReporter.cpp #include <iostream> namespace ApprovalTests { TextDiffReporter::TextDiffReporter() : TextDiffReporter(std::cout) { } TextDiffReporter::TextDiffReporter(std::ostream& stream) : stream_(stream) { std::vector<std::shared_ptr<Reporter>> reporters = { CustomReporter::createForegroundReporter("diff", Type::TEXT, true), CustomReporter::createForegroundReporter( "C:/Windows/System32/fc.exe", Type::TEXT_AND_IMAGE, true)}; m_reporter = std::unique_ptr<Reporter>(new FirstWorkingReporter(reporters)); } bool TextDiffReporter::report(std::string received, std::string approved) const { stream_ << "Comparing files:" << std::endl; stream_ << "received: " << received << std::endl; stream_ << "approved: " << approved << std::endl; const bool result = m_reporter->report(received, approved); if (!result) { stream_ << "TextDiffReporter did not find a working diff " "program\n\n"; } return result; } } // ******************** From: WindowsReporters.cpp namespace ApprovalTests { namespace Windows { VisualStudioCodeReporter::VisualStudioCodeReporter() : GenericDiffReporter(DiffPrograms::Windows::VS_CODE()) { } // ----------------------- Beyond Compare ---------------------------------- BeyondCompare3Reporter::BeyondCompare3Reporter() : GenericDiffReporter(DiffPrograms::Windows::BEYOND_COMPARE_3()) { } BeyondCompare4Reporter::BeyondCompare4Reporter() : GenericDiffReporter(DiffPrograms::Windows::BEYOND_COMPARE_4()) { } BeyondCompareReporter::BeyondCompareReporter() : FirstWorkingReporter( {new BeyondCompare4Reporter(), new BeyondCompare3Reporter()}) { } // ----------------------- Tortoise SVN ------------------------------------ TortoiseImageDiffReporter::TortoiseImageDiffReporter() : GenericDiffReporter(DiffPrograms::Windows::TORTOISE_IMAGE_DIFF()) { } TortoiseTextDiffReporter::TortoiseTextDiffReporter() : GenericDiffReporter(DiffPrograms::Windows::TORTOISE_TEXT_DIFF()) { } TortoiseDiffReporter::TortoiseDiffReporter() : FirstWorkingReporter( {new TortoiseTextDiffReporter(), new TortoiseImageDiffReporter()}) { } // ----------------------- Tortoise Git ------------------------------------ TortoiseGitTextDiffReporter::TortoiseGitTextDiffReporter() : GenericDiffReporter(DiffPrograms::Windows::TORTOISE_GIT_TEXT_DIFF()) { } TortoiseGitImageDiffReporter::TortoiseGitImageDiffReporter() : GenericDiffReporter(DiffPrograms::Windows::TORTOISE_GIT_IMAGE_DIFF()) { } TortoiseGitDiffReporter::TortoiseGitDiffReporter() : FirstWorkingReporter( {new TortoiseGitTextDiffReporter(), new TortoiseGitImageDiffReporter()}) { } // ------------------------------------------------------------------------- WinMergeReporter::WinMergeReporter() : GenericDiffReporter(DiffPrograms::Windows::WIN_MERGE_REPORTER()) { } AraxisMergeReporter::AraxisMergeReporter() : GenericDiffReporter(DiffPrograms::Windows::ARAXIS_MERGE()) { } CodeCompareReporter::CodeCompareReporter() : GenericDiffReporter(DiffPrograms::Windows::CODE_COMPARE()) { } SublimeMergeReporter::SublimeMergeReporter() : GenericDiffReporter(DiffPrograms::Windows::SUBLIME_MERGE()) { launcher.setForeground(true); } KDiff3Reporter::KDiff3Reporter() : GenericDiffReporter(DiffPrograms::Windows::KDIFF3()) { } WindowsDiffReporter::WindowsDiffReporter() : FirstWorkingReporter({ new TortoiseDiffReporter(), // Note that this uses Tortoise SVN Diff new TortoiseGitDiffReporter(), new BeyondCompareReporter(), new WinMergeReporter(), new AraxisMergeReporter(), new CodeCompareReporter(), new SublimeMergeReporter(), new KDiff3Reporter(), new VisualStudioCodeReporter(), }) { } bool WindowsDiffReporter::report(std::string received, std::string approved) const { if (!SystemUtils::isWindowsOs()) { return false; } return FirstWorkingReporter::report(received, approved); } } } // ******************** From: Scrubbers.cpp #include <string> #include <functional> #include <regex> #include <map> namespace ApprovalTests { namespace Scrubbers { std::string doNothing(const std::string& input) { return input; } std::string scrubRegex(const std::string& input, const std::regex& regex, const RegexReplacer& replaceFunction) { std::string result; std::string remainder = input; std::smatch m; while (std::regex_search(remainder, m, regex)) { auto match = m[0]; auto original_matched_text = match.str(); auto replacement = replaceFunction(match); result += std::string(m.prefix()) + replacement; remainder = m.suffix(); } result += remainder; return result; } Scrubber createRegexScrubber(const std::regex& regexPattern, const RegexReplacer& replacer) { return [=](const std::string& input) { return scrubRegex(input, regexPattern, replacer); }; } Scrubber createRegexScrubber(const std::regex& regexPattern, const std::string& replacementText) { return createRegexScrubber( regexPattern, [=](const RegexMatch&) { return replacementText; }); } Scrubber createRegexScrubber(const std::string& regexString, const std::string& replacementText) { if (regexString.empty()) { return doNothing; } return createRegexScrubber(std::regex(regexString), replacementText); } std::string scrubGuid(const std::string& input) { static const std::regex regex("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[" "0-9a-fA-F]{4}-[0-9a-fA-F]{12}"); int matchNumber = 0; std::map<std::string, int> matchIndices; return scrubRegex(input, regex, [&](const RegexMatch& m) { auto guid_match = m.str(); if (matchIndices[guid_match] == 0) { matchIndices[guid_match] = ++matchNumber; } return "guid_" + std::to_string(matchIndices[guid_match]); }); } } } // ******************** From: DateUtils.cpp #include <sstream> #include <iomanip> namespace ApprovalTests { std::tm DateUtils::createTm(int year, int month, int day, int hour, int minute, int second) { std::tm timeinfo = tm(); timeinfo.tm_year = year - 1900; timeinfo.tm_mon = month - 1; timeinfo.tm_mday = day; timeinfo.tm_hour = hour; timeinfo.tm_min = minute; timeinfo.tm_sec = second; return timeinfo; } std::chrono::system_clock::time_point DateUtils::createUtcDateTime(int year, int month, int day, int hour, int minute, int second) // these are UTC values { tm timeinfo = createTm(year, month, day, hour, minute, second); time_t tt = toUtc(timeinfo); return std::chrono::system_clock::from_time_t(tt); } std::string DateUtils::toString(const std::chrono::system_clock::time_point& dateTime) { return toString(dateTime, "%a %Y-%m-%d %H:%M:%S UTC"); } std::string DateUtils::toString(const std::chrono::system_clock::time_point& dateTime, const std::string& format) { time_t tt = std::chrono::system_clock::to_time_t(dateTime); tm tm_value = toUtc(tt); return StringUtils::toString(std::put_time(&tm_value, format.c_str())); } time_t DateUtils::toUtc(std::tm& timeinfo) { #ifdef _WIN32 std::time_t tt = _mkgmtime(&timeinfo); #else time_t tt = timegm(&timeinfo); #endif return tt; } tm DateUtils::toUtc(time_t& tt) { #ifdef _MSC_VER // Visual Studio compiler std::tm tm_value = {}; gmtime_s(&tm_value, &tt); #else tm tm_value = *gmtime(&tt); #endif return tm_value; } } // ******************** From: EmptyFileCreatorByType.cpp namespace { std::map<std::string, ApprovalTests::EmptyFileCreator> defaultEmptyFileCreatorByTypeCreators() { std::map<std::string, ApprovalTests::EmptyFileCreator> creators; ApprovalTests::EmptyFileCreator wibbleCreator = [](std::string fileName) { ApprovalTests::StringWriter s("{}"); s.write(fileName); }; creators[".json"] = wibbleCreator; return creators; } } namespace ApprovalTests { std::map<std::string, ApprovalTests::EmptyFileCreator> EmptyFileCreatorByType::creators_ = defaultEmptyFileCreatorByTypeCreators(); void EmptyFileCreatorByType::registerCreator(const std::string& extensionWithDot, EmptyFileCreator creator) { creators_[extensionWithDot] = std::move(creator); } void EmptyFileCreatorByType::createFile(const std::string& fileName) { for (const auto& creator : creators_) { if (StringUtils::endsWith(fileName, creator.first)) { creator.second(fileName); return; } } EmptyFileCreatorFactory::defaultCreator(fileName); } } // ******************** From: EmptyFileCreatorDisposer.cpp #include <sstream> namespace ApprovalTests { EmptyFileCreatorDisposer::EmptyFileCreatorDisposer(EmptyFileCreator creator) { previous_result = std::move(EmptyFileCreatorFactory::currentCreator); EmptyFileCreatorFactory::currentCreator = std::move(creator); } EmptyFileCreatorDisposer::~EmptyFileCreatorDisposer() { EmptyFileCreatorFactory::currentCreator = std::move(previous_result); } } // ******************** From: EmptyFileCreatorFactory.cpp namespace ApprovalTests { void EmptyFileCreatorFactory::defaultCreator(std::string fullFilePath) { StringWriter s("", ""); s.write(fullFilePath); } EmptyFileCreator EmptyFileCreatorFactory::currentCreator = EmptyFileCreatorByType::createFile; } // ******************** From: ExceptionCollector.cpp namespace ApprovalTests { void ExceptionCollector::gather(std::function<void(void)> functionThatThrows) { try { functionThatThrows(); } catch (const std::exception& e) { exceptionMessages.emplace_back(e.what()); } } ExceptionCollector::~ExceptionCollector() { if (!exceptionMessages.empty()) { exceptionMessages.emplace_back("ERROR: Calling code forgot to call " "exceptionCollector.release()"); } release(); } void ExceptionCollector::release() { if (!exceptionMessages.empty()) { std::stringstream s; s << exceptionMessages.size() << " exceptions were thrown:\n\n"; int count = 1; for (const auto& error : exceptionMessages) { s << count++ << ") " << error << '\n'; } exceptionMessages.clear(); throw std::runtime_error(s.str()); } } } // ******************** From: FileUtils.cpp #include <fstream> #include <sys/stat.h> #include <sstream> namespace ApprovalTests { bool FileUtils::fileExists(const std::string& path) { struct stat info { }; return stat(path.c_str(), &info) == 0; } int FileUtils::fileSize(const std::string& path) { struct stat statbuf { }; int stat_ok = stat(path.c_str(), &statbuf); if (stat_ok == -1) { return -1; } return int(statbuf.st_size); } EmptyFileCreatorDisposer FileUtils::useEmptyFileCreator(EmptyFileCreator creator) { return EmptyFileCreatorDisposer(creator); } void FileUtils::ensureFileExists(const std::string& fullFilePath) { if (!fileExists(fullFilePath)) { EmptyFileCreatorFactory::currentCreator(fullFilePath); } } std::string FileUtils::getDirectory(const std::string& filePath) { auto end = filePath.rfind(SystemUtils::getDirectorySeparator()) + 1; auto directory = filePath.substr(0, end); return directory; } std::string FileUtils::getExtensionWithDot(const std::string& filePath) { std::size_t found = filePath.find_last_of('.'); return filePath.substr(found); } std::string FileUtils::readFileThrowIfMissing(const std::string& fileName) { std::ifstream in(fileName.c_str(), std::ios_base::in); if (!in) { throw std::runtime_error("File does not exist: " + fileName); } std::stringstream written; written << in.rdbuf(); in.close(); std::string text = written.str(); return text; } std::string FileUtils::readFileReturnEmptyIfMissing(const std::string& fileName) { if (FileUtils::fileExists(fileName)) { return readFileThrowIfMissing(fileName); } else { return std::string(); } } void FileUtils::writeToFile(const std::string& filePath, const std::string& content) { std::ofstream out(filePath.c_str(), std::ios::binary | std::ofstream::out); if (!out) { throw std::runtime_error("Unable to write file: " + filePath); } out << content; } } // ******************** From: FileUtilsSystemSpecific.cpp namespace ApprovalTests { std::string FileUtilsSystemSpecific::getCommandLineForCopy( const std::string& source, const std::string& destination, bool isWindows) { if (isWindows) { return std::string("copy /Y ") + "\"" + source + "\" \"" + destination + "\""; } else { return std::string("cp ") + "\"" + source + "\" \"" + destination + "\""; } } void FileUtilsSystemSpecific::copyFile(const std::string& source, const std::string& destination) { auto cmd = getCommandLineForCopy(source, destination, SystemUtils::isWindowsOs()); SystemUtils::runSystemCommandOrThrow(cmd); } } // ******************** From: Grid.cpp namespace ApprovalTests { std::string Grid::print(int width, int height, std::function<void(int, int, std::ostream&)> printCell) { std::stringstream s; for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { printCell(x, y, s); } s << '\n'; } return s.str(); } std::string Grid::print(int width, int height, std::string text) { return print( width, height, [&](int /*x*/, int /*y*/, std::ostream& os) { os << text; }); } } // ******************** From: MachineBlocker.cpp namespace ApprovalTests { MachineBlocker::MachineBlocker(std::string machineName_, bool block_) : machineName(std::move(machineName_)), block(block_) { } MachineBlocker MachineBlocker::onMachineNamed(const std::string& machineName) { return MachineBlocker(machineName, true); } MachineBlocker MachineBlocker::onMachinesNotNamed(const std::string& machineName) { return MachineBlocker(machineName, false); } bool MachineBlocker::isBlockingOnThisMachine() const { const auto isMachine = (SystemUtils::getMachineName() == machineName); return isMachine == block; } } // ******************** From: MoreHelpMessages.cpp #include <iostream> namespace ApprovalTests { void MoreHelpMessages::deprecatedFunctionCalled(const std::string& message, const std::string& file, int lineNumber) { std::cout << "\n***************** Deprecation Warning: ***************\n" << "*\n" << "* " << message << '\n' << "*\n" << "* Deprecated method:\n" << "* " << file << ":" << lineNumber << '\n' << "* Called from:\n" << "* " << ApprovalTestNamer::getCurrentTest().getFileName() << '\n' << "*\n" << "******************************************************\n\n"; } } // ******************** From: Path.cpp namespace ApprovalTests { Path::Path(const std::string& start) : path_(normalizeSeparators(start)) { } std::string Path::toString() const { return toString(separator_); } std::string Path::toString(const std::string& directoryPathSeparator) const { std::string path = removeRedundantDirectorySeparators(path_); if (separator_ == directoryPathSeparator) { return path; } return StringUtils::replaceAll(path, separator_, directoryPathSeparator); } std::string Path::removeRedundantDirectorySeparators(std::string path) const { bool changed = true; while (changed) { std::string reducePath = path; reducePath = StringUtils::replaceAll(reducePath, "//", "/"); reducePath = StringUtils::replaceAll(reducePath, "\\\\", "\\"); changed = (reducePath != path); path = reducePath; }; return path; } Path Path::operator+(const std::string& addition) const { return Path(path_ + addition); } Path Path::operator/(const std::string& addition) const { auto first = path_; if (StringUtils::endsWith(first, separator_)) { first = first.substr(0, path_.size() - 1); } auto second = addition; if (StringUtils::beginsWith(second, separator_)) { second = second.substr(1); } return Path(first + separator_ + second); } Path Path::operator/(const Path addition) const { return *this / addition.path_; } std::string Path::normalizeSeparators(const std::string& path) { auto separator = SystemUtils::getDirectorySeparator(); auto otherSeparator = (separator == "/" ? "\\" : "/"); return StringUtils::replaceAll(path, otherSeparator, separator); } } // ******************** From: StringUtils.cpp #include <cctype> namespace ApprovalTests { APPROVAL_TESTS_NO_DISCARD std::string StringUtils::replaceAll(std::string inText, const std::string& find, const std::string& replaceWith) { size_t start_pos = 0; while ((start_pos = inText.find(find, start_pos)) != std::string::npos) { inText.replace(start_pos, find.length(), replaceWith); start_pos += replaceWith.length(); // Handles case where 'to' is a substring of 'from' } return inText; } APPROVAL_TESTS_NO_DISCARD bool StringUtils::contains(const std::string& inText, const std::string& find) { return inText.find(find, 0) != std::string::npos; } APPROVAL_TESTS_NO_DISCARD std::string StringUtils::toLower(std::string inText) { std::string copy(inText); std::transform(inText.begin(), inText.end(), copy.begin(), [](char c) { return static_cast<char>(tolower(c)); }); return copy; } APPROVAL_TESTS_NO_DISCARD bool StringUtils::beginsWith(std::string value, std::string beginning) { if (value.size() < beginning.size()) { return false; } return std::equal(beginning.begin(), beginning.end(), value.begin()); } APPROVAL_TESTS_NO_DISCARD bool StringUtils::endsWith(std::string value, std::string ending) { if (value.size() < ending.size()) { return false; } return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); } APPROVAL_TESTS_NO_DISCARD std::string StringUtils::leftTrim(std::string s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); })); return s; } APPROVAL_TESTS_NO_DISCARD std::string StringUtils::rightTrim(std::string s) { s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); }) .base(), s.end()); return s; } APPROVAL_TESTS_NO_DISCARD std::string StringUtils::trim(std::string s) { s = leftTrim(s); s = rightTrim(s); return s; } } // ******************** From: SystemUtils.cpp #include <sys/stat.h> namespace ApprovalTests { bool SystemUtils::isWindowsOs() { #ifdef _WIN32 return true; #else return false; #endif } bool SystemUtils::isCygwin() { #ifdef __CYGWIN__ return true; #else return false; #endif } bool SystemUtils::isMacOs() { #ifdef __APPLE__ return true; #else return false; #endif } std::string SystemUtils::getDirectorySeparator() { return isWindowsOs() ? "\\" : "/"; } std::string SystemUtils::checkFilenameCase(const std::string& fullPath) { if (!isWindowsOs() || !FileUtils::fileExists(fullPath)) { return fullPath; } #ifdef _WIN32 _finddata_t findFileData; auto hFind = _findfirst(fullPath.c_str(), &findFileData); if (hFind != -1) { const std::string fixedFilename = findFileData.name; const std::string fixedPath = StringUtils::replaceAll( fullPath, StringUtils::toLower(fixedFilename), fixedFilename); _findclose(hFind); return fixedPath; } #endif return fullPath; } std::string SystemUtils::safeGetEnvForWindows(const char* name) { APPROVAL_TESTS_UNUSED(name); #ifdef _WIN32 // We use getenv_s on Windows, as use of getenv there gives: // warning C4996: 'getenv': This function or variable may be unsafe. Consider using _dupenv_s instead. // To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details. size_t size; getenv_s(&size, nullptr, 0, name); if (size != 0) { std::string result; result.resize(size); getenv_s(&size, &*result.begin(), size, name); result.pop_back(); return result; } #endif return std::string(); } std::string SystemUtils::safeGetEnvForNonWindows(const char* name) { APPROVAL_TESTS_UNUSED(name); char* p = nullptr; #ifndef _WIN32 p = getenv(name); #endif return (p != nullptr) ? p : std::string(); } std::string SystemUtils::safeGetEnv(const char* name) { return isWindowsOs() ? safeGetEnvForWindows(name) : safeGetEnvForNonWindows(name); } std::string SystemUtils::getMachineName() { auto name = safeGetEnv("COMPUTERNAME"); if (!name.empty()) { return name; } name = safeGetEnv("HOSTNAME"); if (!name.empty()) { return name; } return "Unknown Computer"; } void SystemUtils::makeDirectoryForWindows(const std::string& directory) { APPROVAL_TESTS_UNUSED(directory); #ifdef _WIN32 int nError = _mkdir(directory.c_str()); if (nError != 0) { std::string helpMessage = std::string("Unable to create directory: ") + directory; throw std::runtime_error(helpMessage); } #endif } void SystemUtils::makeDirectoryForNonWindows(const std::string& directory) { APPROVAL_TESTS_UNUSED(directory); #ifndef _WIN32 mode_t nMode = 0733; // UNIX style permissions int nError = mkdir(directory.c_str(), nMode); if (nError != 0) { std::string helpMessage = std::string("Unable to create directory: ") + directory; throw std::runtime_error(helpMessage); } #endif } void SystemUtils::makeDirectory(const std::string& directory) { makeDirectoryForWindows(directory); makeDirectoryForNonWindows(directory); } void SystemUtils::ensureDirectoryExists(const std::string& fullDirectoryPath) { if (!FileUtils::fileExists(fullDirectoryPath)) { makeDirectory(fullDirectoryPath); } } void SystemUtils::ensureParentDirectoryExists(const std::string& fullFilePath) { const std::string parentDirectory = FileUtils::getDirectory(fullFilePath); if (!parentDirectory.empty()) { SystemUtils::ensureDirectoryExists(parentDirectory); } } void SystemUtils::runSystemCommandOrThrow(const std::string& command, bool allowNonZeroExitCodes) { int exitCode = system(command.c_str()); if (!allowNonZeroExitCodes && exitCode != 0) { throw std::runtime_error(command + ": failed with exit code " + std::to_string(exitCode)); } } } // ******************** From: ExistingFile.cpp namespace ApprovalTests { std::string ExistingFile::scrub(std::string fileName, const Options& options) { if (options.isUsingDefaultScrubber()) { return fileName; } auto content = FileUtils::readFileThrowIfMissing(fileName); const auto scrubbedContent = options.scrub(content); if (content == scrubbedContent) { deleteScrubbedFile = false; return fileName; } else { std::size_t found = fileName.find_last_of('.'); auto fileExtension = fileName.substr(found); std::string baseName = fileName.substr(0, found); auto newFileName = baseName + ".scrubbed.received" + fileExtension; FileUtils::writeToFile(newFileName, scrubbedContent); deleteScrubbedFile = true; return newFileName; } } ExistingFile::ExistingFile(std::string filePath_, const Options& options) : options_(options) { filePath = scrub(filePath_, options); } std::string ExistingFile::getFileExtensionWithDot() const { return FileUtils::getExtensionWithDot(filePath); } void ExistingFile::write(std::string) const { // do nothing } void ExistingFile::cleanUpReceived(std::string receivedPath) const { if (deleteScrubbedFile && (receivedPath == filePath)) { remove(receivedPath.c_str()); } } ExistingFileNamer ExistingFile::getNamer() { return ExistingFileNamer(filePath, options_); } } // ******************** From: StringWriter.cpp #include <utility> #include <fstream> namespace ApprovalTests { StringWriter::StringWriter(std::string contents, std::string fileExtensionWithDot) : s(std::move(contents)), ext(std::move(fileExtensionWithDot)) { } std::string StringWriter::getFileExtensionWithDot() const { return ext; } void StringWriter::write(std::string path) const { std::ofstream out(path.c_str(), std::ofstream::out); if (!out) { throw std::runtime_error("Unable to write file: " + path); } this->Write(out); out.close(); } void StringWriter::Write(std::ostream& out) const { out << s << "\n"; } void StringWriter::cleanUpReceived(std::string receivedPath) const { remove(receivedPath.c_str()); } } #endif // APPROVAL_TESTS_INCLUDE_CPPS #endif // APPROVAL_TESTS_CPP_APPROVALS_HPP
Become a Patron
Sponsor on GitHub
Donate via PayPal
Compiler Explorer Shop
Source on GitHub
Mailing list
Installed libraries
Wiki
Report an issue
How it works
Contact the author
CE on Mastodon
CE on Bluesky
Statistics
Changelog
Version tree