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
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
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
Zig
Javascript
GIMPLE
Ygen
sway
c++ source #6
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
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.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)
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.latest
x86 djgpp 4.9.4
x86 djgpp 5.5.0
x86 djgpp 6.4.0
x86 djgpp 7.2.0
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.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-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 P2998)
x86-64 clang (experimental P3068)
x86-64 clang (experimental P3309)
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 gcc (P2034 lambdas)
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.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 "mimic++-amalgamated.hpp" #include "CustomSimpleTestFramework.hpp" int main() { mimicpp::Mock<void()> myMock{}; // we have no expectation defined, yet. // No expectation means, the mock shall never be called. expect_unmatched_call( [&] { myMock(); }); // let's setup a simple expectation SCOPED_EXP myMock.expect_call(); // and consume it myMock(); // all expectations are allowed to be matched once by default. // So, this is expected to fail again, because we already matched our expectation once. expect_unmatched_call( [&] { myMock(); }); // we can setup an expectation and require it to match multiple times namespace expect = mimicpp::expect; SCOPED_EXP myMock.expect_call() | expect::twice(); myMock(); // match once myMock(); // match twice // attempts to match already exhausted expectations again! expect_unmatched_call( [&] { myMock(); }); // let's make a mock with overloads mimicpp::Mock< void(int), void(int) const> overloadedMock{}; // expects a call on the non-const overload with 42 as argument SCOPED_EXP overloadedMock.expect_call(42); // The explicitly chosen const version will fail as expected expect_unmatched_call( [&] { std::as_const(overloadedMock)(42); }); overloadedMock(42); // success! // now let's setup an expectation for the const overload, which accepts arguments greater than 42 namespace matches = mimicpp::matches; SCOPED_EXP std::as_const(overloadedMock).expect_call(matches::gt(42)); // fails, because of invalid argument expect_unmatched_call( [&] { std::as_const(overloadedMock)(42); }); std::as_const(overloadedMock)(1337); // success! }
cmake 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
Options
Source code
cmake_minimum_required(VERSION 3.20) project(Mimic++-Playground) add_executable( mimic++-playground main.cpp )
c++ source #2
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
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.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)
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.latest
x86 djgpp 4.9.4
x86 djgpp 5.5.0
x86 djgpp 6.4.0
x86 djgpp 7.2.0
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.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-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 P2998)
x86-64 clang (experimental P3068)
x86-64 clang (experimental P3309)
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 gcc (P2034 lambdas)
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.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
// // Copyright Dominic (DNKpp) Koepke 2024 - 2024. // // Distributed under the Boost Software License, Version 1.0. // // (See accompanying file LICENSE_1_0.txt or copy at // // https://www.boost.org/LICENSE_1_0.txt) #ifndef MIMICPP_MIMICPP_HPP #define MIMICPP_MIMICPP_HPP #pragma once /*** Start of inlined file: Fwd.hpp ***/ // // Copyright Dominic (DNKpp) Koepke 2024 - 2024. // // Distributed under the Boost Software License, Version 1.0. // // (See accompanying file LICENSE_1_0.txt or copy at // // https://www.boost.org/LICENSE_1_0.txt) #ifndef MIMICPP_FWD_HPP #define MIMICPP_FWD_HPP #pragma once #include <string> namespace mimicpp::call { template <typename Return, typename... Args> class Info; } namespace mimicpp { template <typename Signature> class Expectation; enum class MatchResult { none, inapplicable, full }; class CallReport; class MatchReport; class ExpectationReport; using CharT = char; using CharTraitsT = std::char_traits<CharT>; using StringT = std::basic_string<CharT, CharTraitsT>; } #endif /*** End of inlined file: Fwd.hpp ***/ /*** Start of inlined file: Utility.hpp ***/ // // Copyright Dominic (DNKpp) Koepke 2024 - 2024. // // Distributed under the Boost Software License, Version 1.0. // // (See accompanying file LICENSE_1_0.txt or copy at // // https://www.boost.org/LICENSE_1_0.txt) #ifndef MIMICPP_UTILITY_HPP #define MIMICPP_UTILITY_HPP #pragma once #include <cassert> #include <format> #include <source_location> #include <utility> namespace mimicpp { enum class Constness { non_const = 0b01, as_const = 0b10, any = non_const | as_const }; [[nodiscard]] constexpr bool is_matching(const Constness lhs, const Constness rhs) noexcept { using UnderlyingT = std::underlying_type_t<Constness>; return UnderlyingT{0} != (static_cast<UnderlyingT>(lhs) & static_cast<UnderlyingT>(rhs)); } enum class ValueCategory { lvalue = 0b01, rvalue = 0b10, any = lvalue | rvalue }; [[nodiscard]] constexpr bool is_matching(const ValueCategory lhs, const ValueCategory rhs) noexcept { using UnderlyingT = std::underlying_type_t<ValueCategory>; return UnderlyingT{0} != (static_cast<UnderlyingT>(lhs) & static_cast<UnderlyingT>(rhs)); } } template <> struct std::formatter<mimicpp::ValueCategory, mimicpp::CharT> : public std::formatter<std::string_view, mimicpp::CharT> { using ValueCategoryT = mimicpp::ValueCategory; auto format( const ValueCategoryT category, auto& ctx ) const { constexpr auto toString = [](const ValueCategoryT cat) { switch (cat) { case ValueCategoryT::lvalue: return "lvalue"; case ValueCategoryT::rvalue: return "rvalue"; case ValueCategoryT::any: return "any"; } throw std::invalid_argument{"Unknown category value."}; }; return std::formatter<std::string_view, mimicpp::CharT>::format( toString(category), ctx); } }; template <> struct std::formatter<mimicpp::Constness, mimicpp::CharT> : public std::formatter<std::string_view, mimicpp::CharT> { using ConstnessT = mimicpp::Constness; auto format( const ConstnessT category, auto& ctx ) const { constexpr auto toString = [](const ConstnessT value) { switch (value) { case ConstnessT::non_const: return "mutable"; case ConstnessT::as_const: return "const"; case ConstnessT::any: return "any"; } throw std::invalid_argument{"Unknown constness value."}; }; return std::formatter<std::string_view, mimicpp::CharT>::format( toString(category), ctx); } }; namespace mimicpp { template <typename...> struct always_false : public std::bool_constant<false> { }; [[nodiscard]] constexpr bool is_same_source_location( const std::source_location& lhs, const std::source_location& rhs ) noexcept { return std::string_view{lhs.file_name()} == std::string_view{rhs.file_name()} && std::string_view{lhs.function_name()} == std::string_view{rhs.function_name()} && lhs.line() == rhs.line() && lhs.column() == rhs.column(); } template <typename From, typename To> concept explicitly_convertible_to = requires { static_cast<To>(std::declval<From>()); }; template <typename From, typename To> concept nothrow_explicitly_convertible_to = explicitly_convertible_to<From, To> && requires { { static_cast<To>(std::declval<From>()) } noexcept; }; template <typename T> requires std::is_enum_v<T> [[nodiscard]] constexpr std::underlying_type_t<T> to_underlying(const T value) noexcept { return static_cast<std::underlying_type_t<T>>(value); } // GCOVR_EXCL_START #ifdef __cpp_lib_unreachable using std::unreachable; #else /** * \brief Invokes undefined behavior * \see https://en.cppreference.com/w/cpp/utility/unreachable * \note Implementation directly taken from https://en.cppreference.com/w/cpp/utility/unreachable */ [[noreturn]] inline void unreachable() { // Uses compiler specific extensions if possible. // Even if no extension is used, undefined behavior is still raised by // an empty function body and the noreturn attribute. #if defined(_MSC_VER) && !defined(__clang__) // MSVC __assume(false); #else // GCC, Clang __builtin_unreachable(); #endif // ReSharper disable once CppUnreachableCode assert(false); } #endif // GCOVR_EXCL_STOP } #endif /*** End of inlined file: Utility.hpp ***/ /*** Start of inlined file: TypeTraits.hpp ***/ // // Copyright Dominic (DNKpp) Koepke 2024 - 2024. // // Distributed under the Boost Software License, Version 1.0. // // (See accompanying file LICENSE_1_0.txt or copy at // // https://www.boost.org/LICENSE_1_0.txt) #ifndef MIMICPP_TYPE_TRAITS_HPP #define MIMICPP_TYPE_TRAITS_HPP #pragma once #include <tuple> namespace mimicpp { template <typename Signature> struct signature_add_noexcept; template <typename Signature> using signature_add_noexcept_t = typename signature_add_noexcept<Signature>::type; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params...)> { using type = Return(Params...) noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params..., ...)> { using type = Return(Params..., ...) noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params...) noexcept> { using type = Return(Params...) noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params..., ...) noexcept> { using type = Return(Params..., ...) noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params...) const> { using type = Return(Params...) const noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params..., ...) const> { using type = Return(Params..., ...) const noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params...) const noexcept> { using type = Return(Params...) const noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params..., ...) const noexcept> { using type = Return(Params..., ...) const noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params...) &> { using type = Return(Params...) & noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params..., ...) &> { using type = Return(Params..., ...) & noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params...) & noexcept> { using type = Return(Params...) & noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params..., ...) & noexcept> { using type = Return(Params..., ...) & noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params...) const &> { using type = Return(Params...) const & noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params..., ...) const &> { using type = Return(Params..., ...) const & noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params...) const & noexcept> { using type = Return(Params...) const & noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params..., ...) const & noexcept> { using type = Return(Params..., ...) const & noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params...) &&> { using type = Return(Params...) && noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params..., ...) &&> { using type = Return(Params..., ...) && noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params...) && noexcept> { using type = Return(Params...) && noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params..., ...) && noexcept> { using type = Return(Params..., ...) && noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params...) const &&> { using type = Return(Params...) const && noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params..., ...) const &&> { using type = Return(Params..., ...) const && noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params...) const && noexcept> { using type = Return(Params...) const && noexcept; }; template <typename Return, typename... Params> struct signature_add_noexcept<Return(Params..., ...) const && noexcept> { using type = Return(Params..., ...) const && noexcept; }; template <typename Signature> struct signature_remove_noexcept; template <typename Signature> using signature_remove_noexcept_t = typename signature_remove_noexcept<Signature>::type; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params...)> { using type = Return(Params...); }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params..., ...)> { using type = Return(Params..., ...); }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params...) noexcept> { using type = Return(Params...); }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params..., ...) noexcept> { using type = Return(Params..., ...); }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params...) const> { using type = Return(Params...) const; }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params..., ...) const> { using type = Return(Params..., ...) const; }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params...) const noexcept> { using type = Return(Params...) const; }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params..., ...) const noexcept> { using type = Return(Params..., ...) const; }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params...) &> { using type = Return(Params...) &; }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params..., ...) &> { using type = Return(Params..., ...) &; }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params...) & noexcept> { using type = Return(Params...) &; }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params..., ...) & noexcept> { using type = Return(Params..., ...) &; }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params...) const &> { using type = Return(Params...) const &; }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params..., ...) const &> { using type = Return(Params..., ...) const &; }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params...) const & noexcept> { using type = Return(Params...) const &; }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params..., ...) const & noexcept> { using type = Return(Params..., ...) const &; }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params...) &&> { using type = Return(Params...) &&; }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params..., ...) &&> { using type = Return(Params..., ...) &&; }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params...) && noexcept> { using type = Return(Params...) &&; }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params..., ...) && noexcept> { using type = Return(Params..., ...) &&; }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params...) const &&> { using type = Return(Params...) const &&; }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params..., ...) const &&> { using type = Return(Params..., ...) const &&; }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params...) const && noexcept> { using type = Return(Params...) const &&; }; template <typename Return, typename... Params> struct signature_remove_noexcept<Return(Params..., ...) const && noexcept> { using type = Return(Params..., ...) const &&; }; template <typename Signature> struct signature_decay; template <typename Signature> using signature_decay_t = typename signature_decay<Signature>::type; template <typename Return, typename... Params> struct signature_decay<Return(Params...)> { using type = Return(Params...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params...) const> { using type = Return(Params...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params...) &> { using type = Return(Params...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params...) const &> { using type = Return(Params...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params...) &&> { using type = Return(Params...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params...) const &&> { using type = Return(Params...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params...) noexcept> { using type = Return(Params...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params...) const noexcept> { using type = Return(Params...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params...) & noexcept> { using type = Return(Params...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params...) const & noexcept> { using type = Return(Params...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params...) && noexcept> { using type = Return(Params...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params...) const && noexcept> { using type = Return(Params...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params..., ...)> { using type = Return(Params..., ...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params..., ...) const> { using type = Return(Params..., ...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params..., ...) &> { using type = Return(Params..., ...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params..., ...) const &> { using type = Return(Params..., ...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params..., ...) &&> { using type = Return(Params..., ...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params..., ...) const &&> { using type = Return(Params..., ...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params..., ...) noexcept> { using type = Return(Params..., ...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params..., ...) const noexcept> { using type = Return(Params..., ...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params..., ...) & noexcept> { using type = Return(Params..., ...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params..., ...) const & noexcept> { using type = Return(Params..., ...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params..., ...) && noexcept> { using type = Return(Params..., ...); }; template <typename Return, typename... Params> struct signature_decay<Return(Params..., ...) const && noexcept> { using type = Return(Params..., ...); }; template <typename Signature> struct signature_return_type; template <typename Signature> requires std::is_function_v<Signature> struct signature_return_type<Signature> : public signature_return_type<signature_decay_t<Signature>> { }; template <typename Signature> using signature_return_type_t = typename signature_return_type<Signature>::type; template <typename Return, typename... Params> struct signature_return_type<Return(Params...)> { using type = Return; }; template <typename Return, typename... Params> struct signature_return_type<Return(Params..., ...)> { using type = Return; }; template <std::size_t index, typename Signature> struct signature_param_type; template <std::size_t index, typename Signature> requires std::is_function_v<Signature> struct signature_param_type<index, Signature> : public signature_param_type< index, signature_decay_t<Signature>> { }; template <std::size_t index, typename Signature> using signature_param_type_t = typename signature_param_type<index, Signature>::type; template <std::size_t index, typename Return, typename... Params> struct signature_param_type<index, Return(Params...)> : public std::tuple_element<index, std::tuple<Params...>> { }; template <typename Signature> struct signature_param_list; template <typename Signature> requires std::is_function_v<Signature> struct signature_param_list<Signature> : public signature_param_list< signature_decay_t<Signature>> { }; template <typename Signature> using signature_param_list_t = typename signature_param_list<Signature>::type; template <typename Return, typename... Params> struct signature_param_list<Return(Params...)> { using type = std::tuple<Params...>; }; namespace detail { template <typename First, typename Second, bool reversed = false> struct is_overloadable_with : public std::conditional_t< reversed, std::false_type, is_overloadable_with<Second, First, true>> { }; template <typename First, typename Second> requires ( !std::same_as< signature_param_list_t<signature_decay_t<First>>, signature_param_list_t<signature_decay_t<Second>>>) struct is_overloadable_with<First, Second, false> : public std::true_type { }; template <typename Return1, typename Return2, typename... Params, bool reversed> struct is_overloadable_with<Return1(Params...), Return2(Params...) const, reversed> : public std::true_type { }; template <typename Return1, typename Return2, typename... Params, bool reversed> struct is_overloadable_with<Return1(Params...) &, Return2(Params...) const &, reversed> : public std::true_type { }; template <typename Return1, typename Return2, typename... Params, bool reversed> struct is_overloadable_with<Return1(Params...) &, Return2(Params...) &&, reversed> : public std::true_type { }; template <typename Return1, typename Return2, typename... Params, bool reversed> struct is_overloadable_with<Return1(Params...) &, Return2(Params...) const &&, reversed> : public std::true_type { }; template <typename Return1, typename Return2, typename... Params, bool reversed> struct is_overloadable_with<Return1(Params...) const &, Return2(Params...) &&, reversed> : public std::true_type { }; template <typename Return1, typename Return2, typename... Params, bool reversed> struct is_overloadable_with<Return1(Params...) const &, Return2(Params...) const &&, reversed> : public std::true_type { }; template <typename Return1, typename Return2, typename... Params, bool reversed> struct is_overloadable_with<Return1(Params...) &&, Return2(Params...) const &&, reversed> : public std::true_type { }; } template <typename First, typename Second> struct is_overloadable_with : public detail::is_overloadable_with< signature_remove_noexcept_t<First>, signature_remove_noexcept_t<Second>> { }; template <typename First, typename Second> inline constexpr bool is_overloadable_with_v = is_overloadable_with<First, Second>::value; template <typename First, typename... Others> struct is_overload_set; template <typename First> struct is_overload_set<First> : public std::true_type { }; template <typename First, typename Second, typename... Others> struct is_overload_set<First, Second, Others...> : public std::conjunction< is_overloadable_with<First, Second>, is_overload_set<First, Others...>, is_overload_set<Second, Others...>> { }; template <typename First, typename... Others> inline constexpr bool is_overload_set_v = is_overload_set<First, Others...>::value; } #endif /*** End of inlined file: TypeTraits.hpp ***/ /*** Start of inlined file: Printer.hpp ***/ // // Copyright Dominic (DNKpp) Koepke 2024 - 2024. // // Distributed under the Boost Software License, Version 1.0. // // (See accompanying file LICENSE_1_0.txt or copy at // // https://www.boost.org/LICENSE_1_0.txt) #ifndef MIMICPP_PRINTER_HPP #define MIMICPP_PRINTER_HPP #pragma once #include <format> #include <iterator> #include <source_location> #include <sstream> #include <string> #include <string_view> #include <utility> namespace mimicpp { using StringViewT = std::basic_string_view<CharT, CharTraitsT>; using StringStreamT = std::basic_ostringstream<CharT, CharTraitsT>; template <typename T> concept print_iterator = std::output_iterator<T, CharT>; } namespace mimicpp::format { #ifndef _LIBCPP_VERSION using std::format; using std::format_to; using std::vformat; using std::vformat_to; using std::make_format_args; #else // libc++ has some serious trouble when using its std::format implementation. // Let's simply redirect any calls to std::vformat instead. using std::vformat; using std::vformat_to; using std::make_format_args; template <typename... Args> [[nodiscard]] StringT format(const StringViewT fmt, Args&&... args) // NOLINT(cppcoreguidelines-missing-std-forward) { return format::vformat( fmt, std::make_format_args(args...)); } template <class OutputIt, typename... Args> OutputIt format_to(const OutputIt out, const StringViewT fmt, Args&&... args) // NOLINT(cppcoreguidelines-missing-std-forward) { return format::vformat_to( out, fmt, std::make_format_args(args...)); } #endif } namespace mimicpp::custom { template <typename> class Printer; } namespace mimicpp::detail { template <std::size_t priority> struct priority_tag : public priority_tag<priority - 1> { }; template <> struct priority_tag<0> { }; template <print_iterator OutIter, typename T, typename Printer = custom::Printer<std::remove_cvref_t<T>>> OutIter print( OutIter out, T&& value, const priority_tag<5> ) requires requires { { Printer::print(out, std::forward<T>(value)) } -> std::convertible_to<OutIter>; } { return Printer::print(out, std::forward<T>(value)); } template <typename OutIter, std::convertible_to<StringViewT> String> OutIter print( OutIter out, String&& str, priority_tag<4> ) { return format::format_to( out, "\"{}\"", static_cast<StringViewT>(str)); } template <typename OutIter, std::ranges::forward_range Range> OutIter print( OutIter out, Range&& range, priority_tag<3> ); template <typename Char> struct format_context; template <typename Char> using format_context_t = typename format_context<Char>::type; template <> struct format_context<char> { using type = std::format_context; }; template <> struct format_context<wchar_t> { using type = std::wformat_context; }; /** * \brief Determines, whether a complete specialization of ``std::formatter`` for the given (possibly cv-ref qualified) type exists. * \tparam T Type to check. * \tparam Char Used character type. * \details This is an adapted implementation of the ``std::formattable`` concept, which is added c++23. * \note This implementation takes a simple but reasonable shortcut in assuming, that ```Char`` is either ``char`` or ``wchar_t``, * which must not necessarily true. * \see Adapted from here: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2286r8.html#concept-formattable * \see https://en.cppreference.com/w/cpp/utility/format/formattable */ template <class T, class Char> concept formattable = std::semiregular<std::formatter<std::remove_cvref_t<T>, Char>> && requires( std::formatter<std::remove_cvref_t<T>, Char> formatter, T t, format_context_t<Char> formatContext, std::basic_format_parse_context<Char> parseContext ) { { formatter.parse(parseContext) } -> std::same_as<typename std::basic_format_parse_context<Char>::iterator>; { std::as_const(formatter).format(t, formatContext) } -> std::same_as<typename std::remove_reference_t<decltype(formatContext)>::iterator>; }; template <typename OutIter, formattable<CharT> T> OutIter print( OutIter out, T& value, const priority_tag<2> ) { return format::format_to(out, "{}", value); } template <typename OutIter> OutIter print( OutIter out, const std::source_location& loc, const priority_tag<1> ) { return format::format_to( out, "{}[{}:{}], {}", loc.file_name(), loc.line(), loc.column(), loc.function_name()); } template <typename OutIter> OutIter print( OutIter out, auto&, const priority_tag<0> ) { return format::format_to(out, "{{?}}"); } class PrintFn { public: template <print_iterator OutIter, typename T> OutIter operator ()( OutIter out, T&& value ) const { static_assert( requires(const priority_tag<5> tag) { { print(out, std::forward<T>(value), tag) } -> std::convertible_to<OutIter>; }, "The given type is not printable. "); return print( out, std::forward<T>(value), priority_tag<5>{}); } template <typename T> StringT operator ()(T&& value) const { StringStreamT stream{}; operator()( std::ostreambuf_iterator{stream}, std::forward<T>(value)); return std::move(stream).str(); } }; template <typename OutIter, std::ranges::forward_range Range> OutIter print( OutIter out, Range&& range, const priority_tag<3> ) { out = format::format_to(out, "{{ "); auto iter = std::ranges::begin(range); if (const auto end = std::ranges::end(range); iter != end) { constexpr PrintFn print{}; out = print(out, *iter++); for (; iter != end; ++iter) { out = print( format::format_to(out, ", "), *iter); } } return format::format_to(out, " }}"); } } namespace mimicpp { inline constexpr detail::PrintFn print{}; } #endif /*** End of inlined file: Printer.hpp ***/ /*** Start of inlined file: Reporter.hpp ***/ // // Copyright Dominic (DNKpp) Koepke 2024 - 2024. // // Distributed under the Boost Software License, Version 1.0. // // (See accompanying file LICENSE_1_0.txt or copy at // // https://www.boost.org/LICENSE_1_0.txt) #ifndef MIMICPP_REPORTER_HPP #define MIMICPP_REPORTER_HPP #pragma once /*** Start of inlined file: Call.hpp ***/ // // Copyright Dominic (DNKpp) Koepke 2024 - 2024. // // Distributed under the Boost Software License, Version 1.0. // // (See accompanying file LICENSE_1_0.txt or copy at // // https://www.boost.org/LICENSE_1_0.txt) #ifndef MIMICPP_CALL_HPP #define MIMICPP_CALL_HPP #pragma once #include <source_location> #include <tuple> #include <utility> namespace mimicpp::call::detail { template <typename... Args, std::size_t... indices> [[nodiscard]] constexpr bool is_equal_param_list( const std::tuple<std::reference_wrapper<Args>...>& lhs, const std::tuple<std::reference_wrapper<Args>...>& rhs, const std::index_sequence<indices...> ) noexcept { return ( ... && (std::addressof( std::get<indices>(lhs).get()) == std::addressof( std::get<indices>(rhs).get()))); } } namespace mimicpp::call { template <typename Return, typename... Args> class Info { public: using ArgListT = std::tuple<std::reference_wrapper<std::remove_reference_t<Args>>...>; ArgListT args; ValueCategory fromCategory{}; Constness fromConstness{}; std::source_location fromSourceLocation{}; [[nodiscard]] friend bool operator ==(const Info& lhs, const Info& rhs) { return lhs.fromCategory == rhs.fromCategory && lhs.fromConstness == rhs.fromConstness && detail::is_equal_param_list(lhs.args, rhs.args, std::index_sequence_for<Args...>{}) && is_same_source_location(lhs.fromSourceLocation, rhs.fromSourceLocation); } }; template <typename Signature> struct info_for_signature : public info_for_signature<signature_decay_t<Signature>> { }; template <typename Signature> using info_for_signature_t = typename info_for_signature<Signature>::type; template <typename Return, typename... Args> struct info_for_signature<Return(Args...)> { using type = Info<Return, Args...>; }; } #endif /*** End of inlined file: Call.hpp ***/ #include <algorithm> #include <cassert> #include <exception> #include <functional> #include <memory> #include <optional> #include <ranges> #include <typeindex> #include <vector> namespace mimicpp { /** * \defgroup REPORTING_REPORTS reports * \ingroup REPORTING * \brief Contains reports of ``mimic++`` types. * \details Reports are simplified object representations of ``mimic++`` types. In fact, reports are used to communicate with * independent domains (e.g. unit-test frameworks) over the ``IReporter`` interface and are thus designed to provide as much * transparent information as possible, without requiring them to be a generic type. * * \{ */ /** * \brief Contains the extracted info from a typed ``call::Info``. * \details This type is meant to be used to communicate with independent domains via the reporter interface and thus contains * the generic information as plain ``std`` types (e.g. the return type is provided as ``std::type_index`` instead of an actual * type). */ class CallReport { public: class Arg { public: std::type_index typeIndex; StringT stateString; [[nodiscard]] friend bool operator ==(const Arg&, const Arg&) = default; }; std::type_index returnTypeIndex; std::vector<Arg> argDetails{}; std::source_location fromLoc{}; ValueCategory fromCategory{}; Constness fromConstness{}; [[nodiscard]] friend bool operator ==(const CallReport& lhs, const CallReport& rhs) { return lhs.returnTypeIndex == rhs.returnTypeIndex && lhs.argDetails == rhs.argDetails && is_same_source_location(lhs.fromLoc, rhs.fromLoc) && lhs.fromCategory == rhs.fromCategory && lhs.fromConstness == rhs.fromConstness; } }; /** * \brief Generates the call report for a given call info. * \tparam Return The function return type. * \tparam Params The function parameter types. * \param callInfo The call info. * \return The call report. * \relatesalso call::Info */ template <typename Return, typename... Params> [[nodiscard]] CallReport make_call_report(const call::Info<Return, Params...>& callInfo) { return CallReport{ .returnTypeIndex = typeid(Return), .argDetails = std::apply( [](auto&... args) { return std::vector<CallReport::Arg>{ CallReport::Arg{ .typeIndex = typeid(Params), .stateString = mimicpp::print(args.get()) }... }; }, callInfo.args), .fromLoc = callInfo.fromSourceLocation, .fromCategory = callInfo.fromCategory, .fromConstness = callInfo.fromConstness }; } /** * \brief Converts the given report to text. * \param report The report. * \return The report text. * \relatesalso CallReport */ [[nodiscard]] inline StringT stringify_call_report(const CallReport& report) { StringStreamT out{}; format_to( std::ostreambuf_iterator{out}, "call from {}\n", mimicpp::print(report.fromLoc)); format_to( std::ostreambuf_iterator{out}, "constness: {}\n" "value category: {}\n" "return type: {}\n", report.fromConstness, report.fromCategory, report.returnTypeIndex.name()); if (!std::ranges::empty(report.argDetails)) { out << "args:\n"; for (const std::size_t i : std::views::iota(0u, std::ranges::size(report.argDetails))) { format_to( std::ostreambuf_iterator{out}, "\targ[{}]: {{\n" "\t\ttype: {},\n" "\t\tvalue: {}\n" "\t}},\n", i, report.argDetails[i].typeIndex.name(), report.argDetails[i].stateString); } } return std::move(out).str(); } /** * \brief Contains the extracted info from a typed expectation. * \details This type is meant to be used to communicate with independent domains via the reporter interface and thus contains * the generic information as plain ``std`` types. */ class ExpectationReport { public: std::optional<std::source_location> sourceLocation{}; std::optional<StringT> finalizerDescription{}; std::optional<StringT> timesDescription{}; std::vector<std::optional<StringT>> expectationDescriptions{}; [[nodiscard]] friend bool operator ==(const ExpectationReport& lhs, const ExpectationReport& rhs) { return lhs.finalizerDescription == rhs.finalizerDescription && lhs.timesDescription == rhs.timesDescription && lhs.expectationDescriptions == rhs.expectationDescriptions && lhs.sourceLocation.has_value() == rhs.sourceLocation.has_value() && (!lhs.sourceLocation.has_value() || is_same_source_location(*lhs.sourceLocation, *rhs.sourceLocation)); } }; /** * \brief Converts the given report to text. * \param report The report. * \return The report text. * \relatesalso ExpectationReport */ [[nodiscard]] inline StringT stringify_expectation_report(const ExpectationReport& report) { StringStreamT out{}; out << "Expectation report:\n"; if (report.sourceLocation) { out << "from: "; mimicpp::print( std::ostreambuf_iterator{out}, *report.sourceLocation); out << "\n"; } if (report.timesDescription) { format_to( std::ostreambuf_iterator{out}, "times: {}\n", *report.timesDescription); } if (std::ranges::any_of( report.expectationDescriptions, [](const auto& desc) { return desc.has_value(); })) { out << "expects:\n"; for (const auto& desc : report.expectationDescriptions | std::views::filter([](const auto& desc) { return desc.has_value(); })) { format_to( std::ostreambuf_iterator{out}, "\t{},\n", *desc); } } if (report.finalizerDescription) { format_to( std::ostreambuf_iterator{out}, "finally: {}\n", *report.finalizerDescription); } return std::move(out).str(); } /** * \brief Contains the detailed information for match outcomes. * \details This type is meant to be used to communicate with independent domains via the reporter interface and thus contains * the generic information as plain ``std`` types. */ class MatchReport { public: /** * \brief Information about the used finalizer. */ class Finalize { public: std::optional<StringT> description{}; [[nodiscard]] friend bool operator ==(const Finalize&, const Finalize&) = default; }; /** * \brief Information about the current times state. * \details This type contains a description about the current state of the ``times`` policy. This description is gather * in parallel to the ``matches`` (before the ``consume`` step) and thus contains more detailed information about the * outcome. */ class Times { public: bool isApplicable{}; std::optional<StringT> description{}; [[nodiscard]] friend bool operator ==(const Times&, const Times&) = default; }; /** * \brief Information a used expectation policy. * \details This type contains a description about a given expectation policy. */ class Expectation { public: bool isMatching{}; std::optional<StringT> description{}; [[nodiscard]] friend bool operator ==(const Expectation&, const Expectation&) = default; }; std::optional<std::source_location> sourceLocation{}; Finalize finalizeReport{}; Times timesReport{}; std::vector<Expectation> expectationReports{}; [[nodiscard]] friend bool operator ==(const MatchReport& lhs, const MatchReport& rhs) { return lhs.finalizeReport == rhs.finalizeReport && lhs.timesReport == rhs.timesReport && lhs.expectationReports == rhs.expectationReports && lhs.sourceLocation.has_value() == rhs.sourceLocation.has_value() && (!lhs.sourceLocation.has_value() || is_same_source_location(*lhs.sourceLocation, *rhs.sourceLocation)); } }; /** * \brief Determines, whether a match report actually denotes a ``full``, ``inapplicable`` or ``no`` match. * \param report The report to evaluate. * \return The actual result. */ [[nodiscard]] inline MatchResult evaluate_match_report(const MatchReport& report) noexcept { if (!std::ranges::all_of(report.expectationReports, &MatchReport::Expectation::isMatching)) { return MatchResult::none; } if (!report.timesReport.isApplicable) { return MatchResult::inapplicable; } return MatchResult::full; } /** * \brief Converts the given report to text. * \param report The report. * \return The report text. * \relatesalso MatchReport */ [[nodiscard]] inline StringT stringify_match_report(const MatchReport& report) { std::vector<StringT> matchedExpectationDescriptions{}; std::vector<StringT> unmatchedExpectationDescriptions{}; for (const auto& [isMatching, description] : report.expectationReports) { if (description) { if (isMatching) { matchedExpectationDescriptions.emplace_back(*description); } else { unmatchedExpectationDescriptions.emplace_back(*description); } } } StringStreamT out{}; switch (evaluate_match_report(report)) { case MatchResult::full: out << "Matched expectation: {\n"; break; case MatchResult::inapplicable: format_to( std::ostreambuf_iterator{out}, "Inapplicable, but otherwise matched expectation: {{\n" "reason: {}\n", report.timesReport.description.value_or("No reason provided.")); break; case MatchResult::none: out << "Unmatched expectation: {\n"; break; // GCOVR_EXCL_START default: // NOLINT(clang-diagnostic-covered-switch-default) unreachable(); // GCOVR_EXCL_STOP } if (report.sourceLocation) { out << "from: "; mimicpp::print( std::ostreambuf_iterator{out}, *report.sourceLocation); out << "\n"; } if (!std::ranges::empty(unmatchedExpectationDescriptions)) { out << "failed:\n"; for (const auto& desc : unmatchedExpectationDescriptions) { format_to( std::ostreambuf_iterator{out}, "\t{},\n", desc); } } if (!std::ranges::empty(matchedExpectationDescriptions)) { out << "passed:\n"; for (const auto& desc : matchedExpectationDescriptions) { format_to( std::ostreambuf_iterator{out}, "\t{},\n", desc); } } out << "}\n"; return std::move(out).str(); } /** * \} */ /** * \defgroup REPORTING reporting * \brief Contains reporting related symbols * \details Reporting is executed, when something notably has been detected by ``mimic++``; often it is expected, that the reporter * reacts to such a report in a specific manner (e.g. aborting the test case). For example the ``DefaultReporter`` simply throws * exceptions on error reports, while other more specialized reporters handle such cases slightly different (but still abort the * current test). * These specialized Reporters are used to send reports to a specific destination (e.g. the utilized unit-test framework), * which often provide more advanced mechanics for printing failed tests to the users. * * Users may provide their own reporter implementation; e.g. if there is no reporter for the desired unit-test framework. * * At any time there exists exactly one global reporter, which may be directly or indirectly exchanged by users. * Reports are sent to the currently installed reporter via the ``report_xyz`` free-functions. Most of those functions require, that * reports are handled in a specific manner (e.g. ``report_no_matches`` is expected to never return) and custom reporters **must** * follow that specification, otherwise this will lead to undefined behavior. For further details, have a look at the specific * function documentation. * * \note In general users shall not directly interact with the installed reporter, except when they want to replace it. * * \{ */ /** * \brief The reporter interface. * \details This is the central interface to be used, when creating reporters for external domains. */ class IReporter { public: /** * \brief Defaulted virtual destructor. */ virtual ~IReporter() = default; /** * \brief Expects reports about all ``none`` matching expectations. This is only called, if there are no better options available. * \param call The call report. * \param matchReports Reports of all ``none`` matching expectations. * \details This function is called, when no match has been found and there are no other expectations, which are matching but * inapplicable. In fact, this is the fallback reporting mechanism, for unmatched calls. * \note ``matchReports`` may be empty. * * \attention Derived reporter implementations must never return and shall instead leave the function via a thrown exception or * a terminating mechanism (e.g. ``std::terminate``). Otherwise, this will result in undefined behavior. */ [[noreturn]] virtual void report_no_matches( CallReport call, std::vector<MatchReport> matchReports ) = 0; /** * \brief Expects reports about all ``inapplicable`` matching expectations. This is only called, if there are no better options available. * \param call The call report. * \param matchReports Reports of all ``inapplicable`` matching expectations. * \details This function is called, when no applicable match has been found, but actually the call expectations are fulfilled. This in fact * happens, when the ``times`` policy is already saturated (e.g. it was once expected and already matched once) or otherwise not applicable * (e.g. a sequence element is not the current element). * * \attention Derived reporter implementations must never return and shall instead leave the function via a thrown exception or * a terminating mechanism (e.g. ``std::terminate``). Otherwise, this will result in undefined behavior. */ [[noreturn]] virtual void report_inapplicable_matches( CallReport call, std::vector<MatchReport> matchReports ) = 0; /** * \brief Expects the report about a ``full`` matching expectation. * \param call The call report. * \param matchReport Report of the ``full`` matching expectation. * \details This function is called, when a match has been found. There are no other expectations on the behavior of this function; * except the ``noexcept`` guarantee. Implementations shall simply return to the caller. */ virtual void report_full_match( CallReport call, MatchReport matchReport ) noexcept = 0; /** * \brief Expects the report of an unfulfilled expectation. * \param expectationReport The expectation report. * \details This function is called, when an unfulfilled expectation goes out of scope. In fact this happens, when the ``times`` policy is not * satisfied. * * \note In general, it is expected that this function does not return, but throws an exception instead. But, as this function is always called * when an unfulfilled expectation goes out of scope, implementations shall check whether an uncaught exception already exists (e.g. via * ``std::uncaught_exceptions``) before throwing by themselves. * \see ``DefaultReporter::report_unfulfilled_expectation`` for an example. */ virtual void report_unfulfilled_expectation( ExpectationReport expectationReport ) = 0; /** * \brief Expects rather unspecific errors. * \param message The error message. * \details This function is called, when an unspecific error occurs. * * \note In general, it is expected that this function does not return, but throws an exception instead. But, as this function may be called * due to any reason, implementations shall check whether an uncaught exception already exists (e.g. via ``std::uncaught_exceptions``) before * throwing by themselves. * \see ``DefaultReporter::report_error`` for an example. */ virtual void report_error(StringT message) = 0; /** * \brief Expects reports about unhandled exceptions, during ``handle_call``. * \param call The call report. * \param expectationReport The expectation report. * \param exception The exception. * \details This function is called, when an expectation throws during a ``matches`` call. There are no expectations on the behavior of this * function. As this function is called inside a ``catch`` block, throwing exceptions will result in a terminate call. */ virtual void report_unhandled_exception( CallReport call, ExpectationReport expectationReport, std::exception_ptr exception ) = 0; protected: [[nodiscard]] IReporter() = default; IReporter(const IReporter&) = default; IReporter& operator =(const IReporter&) = default; IReporter(IReporter&&) = default; IReporter& operator =(IReporter&&) = default; }; template <typename Data = std::nullptr_t> class Error final : public std::runtime_error { public: [[nodiscard]] explicit Error( const std::string& what, Data&& data = Data{}, const std::source_location& loc = std::source_location::current() ) : std::runtime_error{what}, m_Data{std::move(data)}, m_Loc{loc} { } [[nodiscard]] const Data& data() const noexcept { return m_Data; } [[nodiscard]] const std::source_location& where() const noexcept { return m_Loc; } private: Data m_Data; std::source_location m_Loc; }; /** * \brief A reporter, which creates text messages and reports them via the provided callbacks. * \tparam successReporter The success reporter callback. * \tparam warningReporter The warning reporter callback. * \tparam failReporter The fail reporter callback. This reporter must never return! */ template < std::invocable<const StringT&> auto successReporter, std::invocable<const StringT&> auto warningReporter, std::invocable<const StringT&> auto failReporter > class BasicReporter : public IReporter { public: [[noreturn]] void report_no_matches(const CallReport call, const std::vector<MatchReport> matchReports) override { StringStreamT ss{}; format_to( std::ostreambuf_iterator{ss}, "No match for {}\n", stringify_call_report(call)); if (std::ranges::empty(matchReports)) { ss << "No expectations available.\n"; } else { format_to( std::ostreambuf_iterator{ss}, "{} available expectation(s):\n", std::ranges::size(matchReports)); for (const auto& report : matchReports) { ss << stringify_match_report(report) << "\n"; } } send_fail(ss.str()); } [[noreturn]] void report_inapplicable_matches(const CallReport call, const std::vector<MatchReport> matchReports) override { StringStreamT ss{}; format_to( std::ostreambuf_iterator{ss}, "No applicable match for {}\n", stringify_call_report(call)); ss << "Tested expectations:\n"; for (const auto& report : matchReports) { ss << stringify_match_report(report) << "\n"; } send_fail(ss.str()); } void report_full_match(const CallReport call, const MatchReport matchReport) noexcept override { StringStreamT ss{}; format_to( std::ostreambuf_iterator{ss}, "Found match for {}\n", stringify_call_report(call)); ss << stringify_match_report(matchReport) << "\n"; send_success(ss.str()); } void report_unfulfilled_expectation(const ExpectationReport expectationReport) override { if (0 == std::uncaught_exceptions()) { StringStreamT ss{}; ss << "Unfulfilled expectation:\n" << stringify_expectation_report(expectationReport) << "\n"; send_fail(ss.str()); } } void report_error(const StringT message) override { if (0 == std::uncaught_exceptions()) { send_fail(message); } } void report_unhandled_exception( const CallReport call, const ExpectationReport expectationReport, const std::exception_ptr exception ) override { StringStreamT ss{}; ss << "Unhandled exception: "; try { std::rethrow_exception(exception); } catch (const std::exception& e) { format_to( std::ostreambuf_iterator{ss}, "what: {}\n", e.what()); } catch (...) { ss << "Unknown exception type.\n"; } format_to( std::ostreambuf_iterator{ss}, "while checking expectation:\n" "{}\n", stringify_expectation_report(expectationReport)); format_to( std::ostreambuf_iterator{ss}, "For {}\n", stringify_call_report(call)); send_warning(ss.str()); } private: void send_success(const StringT& msg) { std::invoke(successReporter, msg); } void send_warning(const StringT& msg) { std::invoke(warningReporter, msg); } [[noreturn]] void send_fail(const StringT& msg) { // GCOVR_EXCL_START std::invoke(failReporter, msg); unreachable(); // GCOVR_EXCL_STOP } }; using UnmatchedCallT = Error<std::tuple<CallReport, std::vector<MatchReport>>>; using UnfulfilledExpectationT = Error<ExpectationReport>; /** * \brief The default reporter. */ class DefaultReporter final : public IReporter { public: [[noreturn]] void report_no_matches( CallReport call, std::vector<MatchReport> matchReports ) override { assert( std::ranges::all_of( matchReports, std::bind_front(std::equal_to{}, MatchResult::none), &evaluate_match_report)); const std::source_location loc{call.fromLoc}; throw UnmatchedCallT{ "No match found.", {std::move(call), std::move(matchReports)}, loc }; } [[noreturn]] void report_inapplicable_matches( CallReport call, std::vector<MatchReport> matchReports ) override { assert( std::ranges::all_of( matchReports, std::bind_front(std::equal_to{}, MatchResult::inapplicable), &evaluate_match_report)); const std::source_location loc{call.fromLoc}; throw UnmatchedCallT{ "No applicable match found.", {std::move(call), std::move(matchReports)}, loc }; } void report_full_match( CallReport call, MatchReport matchReport ) noexcept override { assert(MatchResult::full == evaluate_match_report(matchReport)); } void report_unfulfilled_expectation( ExpectationReport expectationReport ) override { if (0 == std::uncaught_exceptions()) { throw UnfulfilledExpectationT{ "Expectation is unfulfilled.", std::move(expectationReport) }; } } void report_error(StringT message) override { if (0 == std::uncaught_exceptions()) { throw Error{message}; } } void report_unhandled_exception( CallReport call, ExpectationReport expectationReport, std::exception_ptr exception ) override { } }; /** * \} */ } namespace mimicpp::detail { [[nodiscard]] inline std::unique_ptr<IReporter>& get_reporter() noexcept { static std::unique_ptr<IReporter> reporter{ std::make_unique<DefaultReporter>() }; return reporter; } [[noreturn]] inline void report_no_matches( CallReport callReport, std::vector<MatchReport> matchReports ) { get_reporter() // GCOVR_EXCL_START ->report_no_matches( // GCOVR_EXCL_STOP std::move(callReport), std::move(matchReports)); // GCOVR_EXCL_START // ReSharper disable once CppUnreachableCode unreachable(); // GCOVR_EXCL_STOP } [[noreturn]] inline void report_inapplicable_matches( CallReport callReport, std::vector<MatchReport> matchReports ) { get_reporter() // GCOVR_EXCL_START ->report_inapplicable_matches( // GCOVR_EXCL_STOP std::move(callReport), std::move(matchReports)); // GCOVR_EXCL_START // ReSharper disable once CppUnreachableCode unreachable(); // GCOVR_EXCL_STOP } inline void report_full_match( CallReport callReport, MatchReport matchReport ) noexcept { get_reporter() ->report_full_match( std::move(callReport), std::move(matchReport)); } inline void report_unfulfilled_expectation( ExpectationReport expectationReport ) { get_reporter() ->report_unfulfilled_expectation(std::move(expectationReport)); } inline void report_error(StringT message) { get_reporter() ->report_error(std::move(message)); } inline void report_unhandled_exception( CallReport callReport, ExpectationReport expectationReport, const std::exception_ptr& exception ) { get_reporter() ->report_unhandled_exception( std::move(callReport), std::move(expectationReport), exception); } } namespace mimicpp { /** * \brief Replaces the previous reporter with a newly constructed one. * \tparam T The desired reporter type. * \tparam Args The constructor argument types for ``T``. * \param args The constructor arguments. * \ingroup REPORTING * \details This function accesses the globally available reporter and replaces it with a new instance. */ template <std::derived_from<IReporter> T, typename... Args> requires std::constructible_from<T, Args...> void install_reporter(Args&&... args) // NOLINT(cppcoreguidelines-missing-std-forward) { detail::get_reporter() = std::make_unique<T>( std::forward<Args>(args)...); } namespace detail { template <typename T> class ReporterInstaller { public: template <typename... Args> explicit ReporterInstaller(Args&&... args) { install_reporter<T>( std::forward<Args>(args)...); } }; } /** * \defgroup REPORTING_ADAPTERS test framework adapters * \ingroup REPORTING * \brief Reporter integrations for various third-party frameworks. * \details These reporters are specialized implementations, which provide seamless integrations of ``mimic++`` into the desired * unit-test framework. Integrations are enabled by simply including the specific header into any source file. The include order * doesn't matter. * * \note Including multiple headers of the ``adapters`` subdirectory into one executable is possible, but with caveats. It's unspecified * which reporter will be active at the program start. So, if you need multiple reporters in one executable, you should explicitly * install the desired reporter on a per test case basis. * *\{ */ } #endif /*** End of inlined file: Reporter.hpp ***/ /*** Start of inlined file: Sequence.hpp ***/ // // Copyright Dominic (DNKpp) Koepke 2024 - 2024. // // Distributed under the Boost Software License, Version 1.0. // // (See accompanying file LICENSE_1_0.txt or copy at // // https://www.boost.org/LICENSE_1_0.txt) #ifndef MIMICPP_SEQUENCE_HPP #define MIMICPP_SEQUENCE_HPP #pragma once #include <algorithm> #include <array> #include <cassert> #include <functional> namespace mimicpp::expectation_policies { class Sequence; } namespace mimicpp { /** * \defgroup EXPECTATION_SEQUENCE sequence * \ingroup EXPECTATION * \ingroup EXPECTATION_TIMES * \brief Sequences enable deterministic ordering between multiple expectations. * \details Their aim is to provide a convenient way for users, to circumvent the rather loosely ordering of expectations, * which is by design. By default, if two or more expectations would match a call, the last created one is used. * If multiple expectations are built one after another, that may be rather unintuitive design, but when it comes to * scoping, it's usually preferable to match the expectations from within the current scope, even if there exist * another similar expectation from the outside scope. * * As I have no idea how to use one strategy for one case and the other for the other case, I decided to take the same * route as ``trompeloeil``. * \see https://github.com/rollbear/trompeloeil/blob/main/docs/CookBook.md#sequences * * Either way, this is sometimes not enough, when we want to enforce deterministic ordering between two or more expectations. * That's where sequences come into play. The expectation, which gets attached first must match before all subsequent * expectations. If that one is fulfilled, the next one in the row must be matched; and so on. * \snippet Sequences.cpp sequence * * Sequences can also enforce orders on expectations, which refer to different mocks. * \snippet Sequences.cpp sequence multiple mocks * * Sequenced and non-sequenced expectations may be arbitrarily mixed; even if this can be very difficult to trace, by * simply reviewing the code. * \snippet Sequences.cpp sequence mixed * * It's totally fine to attach expectations to sequences, which are already queried for matches. Sequences do not have * to be setup in one go. * * # Thread-Safety * Sequences are not thread-safe and are never intended to be. If one attempts to enforce a strong ordering between * multiple threads without any explicit synchronisation, that attempt is doomed to fail. * * # Afterthoughts * Sequence policies are treated as ``times_policy`` and therefore can not be mixed with e.g. ``Times``. * At a first glance this seems rather unintuitive, as a sequence does not actually mind how often an expectation * is matched, but rather if one expectation has been matched before another. This being true, there is still a * huge intersection between those cases, because a sequence still must know how often an expectation is expected * to match. This would lead to higher coupling between two rather unrelated domains, but isn't the actual deal-breaker. * The actual reason is, that both, ``Times`` and ``RuntimeTimes`` are rather permissive designed and thus allow for * a wide range of valid configurations. Especially the cases, where a range of possible calls is configured (like * ``at_least``), makes it very hard for a sequence to reliably determine, whether an expectation shall match or not. * When users define expectations not precise enough, this quickly leads to ambiguities between multiple expectations * which may and will result in surprising outcomes. *\{ */ /** * \brief Strong type for internally used sequence ids. */ enum class SequenceId : std::size_t { }; /** * \} */ namespace detail { class Sequence { public: ~Sequence() noexcept(false) { if (m_Current != m_Entries.size()) { report_error( format::format( "Unfulfilled sequence. {} out of {} expectation(s) where fully consumed.", m_Current, m_Entries.size())); } } [[nodiscard]] Sequence() = default; Sequence(const Sequence&) = delete; Sequence& operator =(const Sequence&) = delete; Sequence(Sequence&&) = delete; Sequence& operator =(Sequence&&) = delete; [[nodiscard]] constexpr bool is_consumable(const SequenceId id) const noexcept { assert(to_underlying(id) < m_Entries.size()); return m_Current == to_underlying(id); } [[nodiscard]] constexpr bool is_saturated(const SequenceId id) const noexcept { assert(to_underlying(id) < m_Entries.size()); const auto [amount, counter] = m_Entries[to_underlying(id)]; return amount == counter; } constexpr void consume(const SequenceId id) noexcept { assert(is_consumable(id)); if (auto& [amount, counter] = m_Entries[m_Current]; amount == ++counter) { ++m_Current; } } [[nodiscard]] constexpr SequenceId add(const std::size_t count) { if (count == 0) { throw std::invalid_argument{"Count must be greater than 0."}; } m_Entries.emplace_back(count, 0); return SequenceId{m_Entries.size() - 1}; } private: struct entry { std::size_t amount{}; std::size_t counter{}; }; std::vector<entry> m_Entries{}; std::size_t m_Current{}; }; } /** * \brief The user level sequence object. * \ingroup EXPECTATION_SEQUENCE * \details This class is just a very thin wrapper and does nothing by its own. It just exists, so that users * have something they can attach expectations to. In fact, objects of this type may even go out of scope before * the attached expectations are destroyed. */ class Sequence { friend class expectation_policies::Sequence; public: ~Sequence() = default; [[nodiscard]] Sequence() = default; Sequence(const Sequence&) = delete; Sequence& operator =(const Sequence&) = delete; Sequence(Sequence&&) = delete; Sequence& operator =(Sequence&&) = delete; private: std::shared_ptr<detail::Sequence> m_Sequence{ std::make_shared<detail::Sequence>() }; }; } namespace mimicpp::expectation_policies { class Sequence { public: ~Sequence() = default; // ReSharper disable once CppParameterMayBeConstPtrOrRef explicit Sequence( const std::span<const std::reference_wrapper<mimicpp::Sequence>> sequences, const std::size_t times ) { m_SequenceInfos.reserve(sequences.size()); // ReSharper disable once CppRangeBasedForIncompatibleReference for (mimicpp::Sequence& sequence : sequences) { m_SequenceInfos.emplace_back( sequence.m_Sequence, sequence.m_Sequence->add(times)); } } Sequence(const Sequence&) = delete; Sequence& operator =(const Sequence&) = delete; Sequence(Sequence&&) = default; Sequence& operator =(Sequence&&) = default; [[nodiscard]] constexpr bool is_satisfied() const noexcept { return std::ranges::all_of( m_SequenceInfos, [](const entry& info){ return info.sequence->is_saturated(info.id); }); } [[nodiscard]] constexpr bool is_applicable() const noexcept { return std::ranges::all_of( m_SequenceInfos, [](const entry& info){ return info.sequence->is_consumable(info.id); }); } [[nodiscard]] StringT describe_state() const { if (is_applicable()) { return "applicable: Sequence element expects further matches."; } if (is_satisfied()) { return "inapplicable: Sequence element is already saturated."; } return "inapplicable: Sequence element is not the current element."; } // ReSharper disable once CppMemberFunctionMayBeConst constexpr void consume() noexcept { assert(is_applicable()); for (auto& [sequence, id] : m_SequenceInfos) { sequence->consume(id); } } private: struct entry { std::shared_ptr<mimicpp::detail::Sequence> sequence; SequenceId id; }; std::vector<entry> m_SequenceInfos; }; } namespace mimicpp::expect { /** * \brief Attaches the expectation onto a sequence. * \ingroup EXPECTATION_REQUIREMENT_TIMES * \ingroup EXPECTATION_SEQUENCE * \param sequence The sequence to be attached to. * \param times The expected times. * \snippet Sequences.cpp sequence * \snippet Sequences.cpp sequence mixed */ [[nodiscard]] inline auto in_sequence(Sequence& sequence, const std::size_t times = 1u) { const std::array collection{std::ref(sequence)}; return expectation_policies::Sequence{ collection, times }; } /** * \brief Attaches the expectation onto the listed sequences. * \ingroup EXPECTATION_REQUIREMENT_TIMES * \ingroup EXPECTATION_SEQUENCE * \param sequences The sequences to be attached to. * \param times The expected times. * \snippet Sequences.cpp sequence multiple sequences */ template <std::size_t size> [[nodiscard]] auto in_sequences( const std::reference_wrapper<Sequence> (&sequences)[size], const std::size_t times = 1u ) { static_assert( 0u < size, "Zero sequences are not allowed. Use times instead."); return expectation_policies::Sequence{ sequences, times }; } } #endif /*** End of inlined file: Sequence.hpp ***/ /*** Start of inlined file: Matcher.hpp ***/ // // Copyright Dominic (DNKpp) Koepke 2024 - 2024. // // Distributed under the Boost Software License, Version 1.0. // // (See accompanying file LICENSE_1_0.txt or copy at // // https://www.boost.org/LICENSE_1_0.txt) #ifndef MIMICPP_MATCHER_HPP #define MIMICPP_MATCHER_HPP #pragma once #include <algorithm> #include <concepts> #include <functional> #include <optional> #include <ranges> #include <tuple> #include <type_traits> namespace mimicpp { template <typename T, typename Target> concept matcher_for = std::same_as<T, std::remove_cvref_t<T>> && std::is_move_constructible_v<T> && std::destructible<T> && requires(const T& matcher, Target& target) { { matcher.matches(target) } -> std::convertible_to<bool>; { matcher.describe() } -> std::convertible_to<std::optional<StringT>>; }; /** * \brief Generic matcher and the basic building block of most of the built-in matchers. * \tparam Predicate The predicate type. * \tparam AdditionalArgs Addition argument types. * \ingroup EXPECTATION_REQUIREMENT * \ingroup EXPECTATION_MATCHER */ template <typename Predicate, typename... AdditionalArgs> requires std::is_move_constructible_v<Predicate> && (... && std::is_move_constructible_v<AdditionalArgs>) class PredicateMatcher { public: [[nodiscard]] explicit constexpr PredicateMatcher( Predicate predicate, StringT fmt, StringT invertedFmt, std::tuple<AdditionalArgs...> additionalArgs = std::tuple{} ) noexcept(std::is_nothrow_move_constructible_v<Predicate> && (... && std::is_nothrow_move_constructible_v<AdditionalArgs>)) : m_Predicate{std::move(predicate)}, m_FormatString{std::move(fmt)}, m_InvertedFormatString{std::move(invertedFmt)}, m_AdditionalArgs{std::move(additionalArgs)} { } template <typename T> requires std::predicate<const Predicate&, T&, const AdditionalArgs&...> [[nodiscard]] constexpr bool matches( T& target ) const noexcept(std::is_nothrow_invocable_v<const Predicate&, T&, const AdditionalArgs&...>) { return std::apply( [&, this](auto&... additionalArgs) { return std::invoke( m_Predicate, target, additionalArgs...); }, m_AdditionalArgs); } [[nodiscard]] constexpr StringT describe() const { return std::apply( [&, this](auto&... additionalArgs) { // std::make_format_args requires lvalue-refs, so let's transform rvalue-refs to const lvalue-refs constexpr auto makeLvalue = [](auto&& val) noexcept -> const auto& { return val; }; return format::vformat( m_FormatString, format::make_format_args( makeLvalue(mimicpp::print(additionalArgs))...)); }, m_AdditionalArgs); } [[nodiscard]] constexpr auto operator !() const & requires std::is_copy_constructible_v<Predicate> && (... && std::is_copy_constructible_v<AdditionalArgs>) { return make_inverted( m_Predicate, m_InvertedFormatString, m_FormatString, std::move(m_AdditionalArgs)); } [[nodiscard]] constexpr auto operator !() && { return make_inverted( std::move(m_Predicate), std::move(m_InvertedFormatString), std::move(m_FormatString), std::move(m_AdditionalArgs)); } private: [[no_unique_address]] Predicate m_Predicate; StringT m_FormatString; StringT m_InvertedFormatString; mutable std::tuple<AdditionalArgs...> m_AdditionalArgs{}; template <typename Fn> [[nodiscard]] static constexpr auto make_inverted( Fn&& fn, StringT fmt, StringT invertedFmt, std::tuple<AdditionalArgs...> tuple ) { using NotFnT = decltype(std::not_fn(std::forward<Fn>(fn))); return PredicateMatcher<NotFnT, AdditionalArgs...>{ std::not_fn(std::forward<Fn>(fn)), std::move(fmt), std::move(invertedFmt), std::move(tuple) }; } }; /** * \brief Matcher, which never fails. * \ingroup EXPECTATION_REQUIREMENT * \ingroup EXPECTATION_MATCHER * \snippet Requirements.cpp matcher wildcard */ class WildcardMatcher { public: static constexpr bool matches(auto&& target) noexcept { return true; } static constexpr std::nullopt_t describe() noexcept { return std::nullopt; } }; } namespace mimicpp::matches { /** * \defgroup EXPECTATION_MATCHER matchers * \ingroup EXPECTATION_REQUIREMENT * \brief Matchers check various argument properties. * \details Matchers can be used to check various argument properties and are highly customizable. In general, * they simply compare their arguments with a pre-defined predicate, but also provide a meaningful description. * * \attention Matchers receive their arguments as possibly non-const, which is due to workaround some restrictions * on const qualified views. Either way, matchers should never modify any of their arguments. * * # Matching arguments * In general matchers can be applied via the ``expect::arg<n>`` factory, but they can also be directly used * at the expect statement. * \snippet Requirements.cpp expect::arg * \snippet Requirements.cpp expect arg matcher * * \details For equality testing, there exists an even shorter syntax. * \snippet Requirements.cpp expect arg equal short * * \details Most of the built-in matchers support the inversion operator (``operator !``), which then tests for the opposite * condition. * \snippet Requirements.cpp matcher inverted * * # Custom Matcher * Matchers are highly customizable. In fact, any type which satisfies ``matcher_for`` concept can be used. * There exists no base or interface type, but the ``PredicateMatcher`` servers as a convenient generic type, which * simply contains a predicate, a format string and optional additional arguments. But, this is just one option. If * you have some very specific needs, go and create your matcher from scratch. * \snippet Requirements.cpp matcher predicate matcher * *\{ */ /** * \brief The wildcard matcher, always matching. * \snippet Requirements.cpp matcher wildcard */ inline constexpr WildcardMatcher _{}; /** * \brief Tests, whether the target compares equal to the expected value. * \tparam T Expected type. * \param value Expected value. */ template <typename T> [[nodiscard]] constexpr auto eq(T&& value) { return PredicateMatcher{ std::ranges::equal_to{}, "== {}", "!= {}", std::tuple{std::forward<T>(value)} }; } /** * \brief Tests, whether the target compares not equal to the expected value. * \tparam T Expected type. * \param value Expected value. */ template <typename T> [[nodiscard]] constexpr auto ne(T&& value) { return PredicateMatcher{ std::ranges::not_equal_to{}, "!= {}", "== {}", std::tuple{std::forward<T>(value)} }; } /** * \brief Tests, whether the target is less than the expected value. * \tparam T Expected type. * \param value Expected value. */ template <typename T> [[nodiscard]] constexpr auto lt(T&& value) { return PredicateMatcher{ std::ranges::less{}, "< {}", ">= {}", std::tuple{std::forward<T>(value)} }; } /** * \brief Tests, whether the target is less than or equal to the expected value. * \tparam T Expected type. * \param value Expected value. */ template <typename T> [[nodiscard]] constexpr auto le(T&& value) { return PredicateMatcher{ std::ranges::less_equal{}, "<= {}", "> {}", std::tuple{std::forward<T>(value)} }; } /** * \brief Tests, whether the target is greater than the expected value. * \tparam T Expected type. * \param value Expected value. */ template <typename T> [[nodiscard]] constexpr auto gt(T&& value) { return PredicateMatcher{ std::ranges::greater{}, "> {}", "<= {}", std::tuple{std::forward<T>(value)} }; } /** * \brief Tests, whether the target is greater than or equal to the expected value. * \tparam T Expected type. * \param value Expected value. */ template <typename T> [[nodiscard]] constexpr auto ge(T&& value) { return PredicateMatcher{ std::ranges::greater_equal{}, ">= {}", "< {}", std::tuple{std::forward<T>(value)} }; } /** * \brief Tests, whether the target fulfills the given predicate. * \tparam UnaryPredicate Predicate type. * \param predicate The predicate to test. * \param description The formatting string. * \param invertedDescription The formatting string for the inversion. * \snippet Requirements.cpp matcher predicate */ template <typename UnaryPredicate> [[nodiscard]] constexpr auto predicate( UnaryPredicate&& predicate, StringT description = "passes predicate", StringT invertedDescription = "fails predicate" ) { return PredicateMatcher{ std::forward<UnaryPredicate>(predicate), std::move(description), std::move(invertedDescription), }; } /** * \} */ } namespace mimicpp::matches::str { /** * \defgroup EXPECTATION_MATCHERS_STRING string matchers * \ingroup EXPECTATION_REQUIREMENT * \ingroup EXPECTATION_MATCHER * \brief String specific matchers. * *\{ */ /** * \brief Tests, whether the target string compares equal to the expected string. * \tparam Char The character type. * \tparam Traits The character traits type. * \tparam Allocator The allocator type. * \param expected The expected string. */ template <typename Char, typename Traits, typename Allocator> [[nodiscard]] constexpr auto eq(std::basic_string<Char, Traits, Allocator> expected) { using ViewT = std::basic_string_view<Char>; return PredicateMatcher{ [](const ViewT target, const ViewT exp) { return target == exp; }, "is equal to {}", "is not equal to {}", std::tuple{std::move(expected)} }; } /** * \brief Tests, whether the target string compares equal to the expected string. * \tparam Char The character type. * \param expected The expected string. */ template <typename Char> [[nodiscard]] constexpr auto eq(const Char* expected) { return eq( std::basic_string<Char>{expected}); } /** * \} */ } namespace mimicpp::matches::range { /** * \defgroup EXPECTATION_MATCHER_RANGE range matchers * \ingroup EXPECTATION_REQUIREMENT * \ingroup EXPECTATION_MATCHER * \brief Range specific matchers. * \snippet Requirements.cpp matcher range sorted *\{ */ /** * \brief Tests, whether the target range compares equal to the expected range, by comparing them element-wise. * \tparam Range Expected range type. * \tparam Comparator Comparator type. * \param expected The expected range. * \param comparator The comparator. */ template <std::ranges::forward_range Range, typename Comparator = std::equal_to<>> [[nodiscard]] constexpr auto eq(Range&& expected, Comparator comparator = Comparator{}) { return PredicateMatcher{ [comp = std::move(comparator)] <typename Target>(Target&& target, auto& range) // NOLINT(cppcoreguidelines-missing-std-forward) requires std::predicate< const Comparator&, std::ranges::range_reference_t<Target>, std::ranges::range_reference_t<Range>> { return std::ranges::equal( target, range, std::ref(comp)); }, "elements are {}", "elements are not {}", std::tuple{std::views::all(std::forward<Range>(expected))} }; } /** * \brief Tests, whether the target range is a permutation of the expected range, by comparing them element-wise. * \tparam Range Expected range type. * \tparam Comparator Comparator type. * \param expected The expected range. * \param comparator The comparator. */ template <std::ranges::forward_range Range, typename Comparator = std::equal_to<>> [[nodiscard]] constexpr auto unordered_eq(Range&& expected, Comparator comparator = Comparator{}) { return PredicateMatcher{ [comp = std::move(comparator)]<typename Target >(Target&& target, auto& range) // NOLINT(cppcoreguidelines-missing-std-forward) requires std::predicate< const Comparator&, std::ranges::range_reference_t<Target>, std::ranges::range_reference_t<Range>> { return std::ranges::is_permutation( target, range, std::ref(comp)); }, "is a permutation of {}", "is not a permutation of {}", std::tuple{std::views::all(std::forward<Range>(expected))} }; } /** * \brief Tests, whether the target range is sorted, by applying the relation on each adjacent elements. * \tparam Relation Relation type. * \param relation The relation. */ template <typename Relation = std::ranges::less> [[nodiscard]] constexpr auto is_sorted(Relation relation = Relation{}) { return PredicateMatcher{ [rel = std::move(relation)]<typename Target>(Target&& target) // NOLINT(cppcoreguidelines-missing-std-forward) requires std::equivalence_relation< const Relation&, std::ranges::range_reference_t<Target>, std::ranges::range_reference_t<Target>> { return std::ranges::is_sorted( target, std::ref(rel)); }, "is a sorted range", "is an unsorted range" }; } /** * \brief Tests, whether the target range is empty. */ [[nodiscard]] constexpr auto is_empty() { return PredicateMatcher{ [](std::ranges::range auto&& target) { return std::ranges::empty(target); }, "is an empty range", "is not an empty range" }; } /** * \brief Tests, whether the target range has the expected size. * \param expected The expected size. */ [[nodiscard]] constexpr auto has_size(const std::integral auto expected) { return PredicateMatcher{ [](std::ranges::range auto&& target, const std::integral auto size) { return std::cmp_equal( size, std::ranges::size(target)); }, "has size of {}", "has different size than {}", std::tuple{expected} }; } /** * \} */ } #endif /*** End of inlined file: Matcher.hpp ***/ /*** Start of inlined file: ExpectationPolicies.hpp ***/ // // Copyright Dominic (DNKpp) Koepke 2024 - 2024. // // Distributed under the Boost Software License, Version 1.0. // // (See accompanying file LICENSE_1_0.txt or copy at // // https://www.boost.org/LICENSE_1_0.txt) #ifndef MIMICPP_EXPECTATION_POLICIES_HPP #define MIMICPP_EXPECTATION_POLICIES_HPP #pragma once /*** Start of inlined file: Expectation.hpp ***/ // // Copyright Dominic (DNKpp) Koepke 2024 - 2024. // // Distributed under the Boost Software License, Version 1.0. // // (See accompanying file LICENSE_1_0.txt or copy at // // https://www.boost.org/LICENSE_1_0.txt) #ifndef MIMICPP_EXPECTATION_HPP #define MIMICPP_EXPECTATION_HPP #pragma once #include <cassert> #include <concepts> #include <memory> #include <mutex> #include <ranges> #include <stdexcept> #include <tuple> #include <utility> #include <vector> namespace mimicpp::detail { template <typename Return, typename... Params, typename Signature> std::optional<MatchReport> make_match_report( const call::Info<Return, Params...>& call, const Expectation<Signature>& expectation ) noexcept { try { return expectation.matches(call); } catch (...) { report_unhandled_exception( make_call_report(call), expectation.report(), std::current_exception()); } return std::nullopt; } } namespace mimicpp { template <typename Signature> class Expectation { public: using CallInfoT = call::info_for_signature_t<Signature>; using ReturnT = signature_return_type_t<Signature>; virtual ~Expectation() = default; [[nodiscard]] Expectation() = default; Expectation(const Expectation&) = delete; Expectation& operator =(const Expectation&) = delete; Expectation(Expectation&&) = delete; Expectation& operator =(Expectation&&) = delete; [[nodiscard]] virtual ExpectationReport report() const = 0; [[nodiscard]] virtual bool is_satisfied() const noexcept = 0; [[nodiscard]] virtual MatchReport matches(const CallInfoT& call) const = 0; virtual void consume(const CallInfoT& call) = 0; [[nodiscard]] virtual constexpr ReturnT finalize_call(const CallInfoT& call) = 0; }; template <typename Signature> class ExpectationCollection { public: using CallInfoT = call::info_for_signature_t<Signature>; using ExpectationT = Expectation<Signature>; using ReturnT = signature_return_type_t<Signature>; ~ExpectationCollection() = default; [[nodiscard]] ExpectationCollection() = default; ExpectationCollection(const ExpectationCollection&) = delete; ExpectationCollection& operator =(const ExpectationCollection&) = delete; [[nodiscard]] ExpectationCollection(ExpectationCollection&&) = default; ExpectationCollection& operator =(ExpectationCollection&&) = default; void push(std::shared_ptr<ExpectationT> expectation) { const std::scoped_lock lock{m_ExpectationsMx}; assert( std::ranges::find(m_Expectations, expectation) == std::ranges::end(m_Expectations) && "Expectation already belongs to this storage."); m_Expectations.emplace_back(std::move(expectation)); } void remove(std::shared_ptr<ExpectationT> expectation) { const std::scoped_lock lock{m_ExpectationsMx}; auto iter = std::ranges::find(m_Expectations, expectation); assert(iter != std::ranges::end(m_Expectations) && "Expectation does not belong to this storage."); m_Expectations.erase(iter); if (!expectation->is_satisfied()) { detail::report_unfulfilled_expectation( expectation->report()); } } [[nodiscard]] ReturnT handle_call(const CallInfoT& call) { std::vector<MatchReport> noMatches{}; std::vector<MatchReport> inapplicableMatches{}; for (const std::scoped_lock lock{m_ExpectationsMx}; auto& exp : m_Expectations | std::views::reverse) { if (std::optional matchReport = detail::make_match_report(call, *exp)) { switch (evaluate_match_report(*matchReport)) { using enum MatchResult; case none: noMatches.emplace_back(*std::move(matchReport)); break; case inapplicable: inapplicableMatches.emplace_back(*std::move(matchReport)); break; case full: detail::report_full_match( make_call_report(call), *std::move(matchReport)); exp->consume(call); return exp->finalize_call(call); // GCOVR_EXCL_START default: unreachable(); // GCOVR_EXCL_STOP } } } if (!std::ranges::empty(inapplicableMatches)) { detail::report_inapplicable_matches( make_call_report(call), std::move(inapplicableMatches)); } detail::report_no_matches( make_call_report(call), std::move(noMatches)); } private: std::vector<std::shared_ptr<ExpectationT>> m_Expectations{}; std::mutex m_ExpectationsMx{}; }; template <typename T, typename Signature> concept expectation_policy_for = std::is_move_constructible_v<T> && std::is_destructible_v<T> && std::same_as<T, std::remove_cvref_t<T>> && requires(T& policy, const call::info_for_signature_t<Signature>& info) { { std::as_const(policy).is_satisfied() } noexcept -> std::convertible_to<bool>; { std::as_const(policy).matches(info) } -> std::convertible_to<bool>; { std::as_const(policy).describe() } -> std::convertible_to<std::optional<StringT>>; { policy.consume(info) }; }; template <typename T, typename Signature> concept finalize_policy_for = std::is_move_constructible_v<T> && std::is_destructible_v<T> && std::same_as<T, std::remove_cvref_t<T>> && requires(T& policy, const call::info_for_signature_t<Signature>& info) { { policy.finalize_call(info) } -> std::convertible_to<signature_return_type_t<Signature>>; }; template <typename T> concept times_policy = std::is_move_constructible_v<T> && std::is_destructible_v<T> && std::same_as<T, std::remove_cvref_t<T>> && requires(T& policy) { { std::as_const(policy).is_satisfied() } noexcept -> std::convertible_to<bool>; { std::as_const(policy).is_applicable() } noexcept -> std::convertible_to<bool>; { std::as_const(policy).describe_state() } -> std::convertible_to<std::optional<StringT>>; policy.consume(); }; template < typename Signature, times_policy TimesPolicy, finalize_policy_for<Signature> FinalizePolicy, expectation_policy_for<Signature>... Policies > class BasicExpectation final : public Expectation<Signature> { public: using TimesT = TimesPolicy; using FinalizerT = FinalizePolicy; using PolicyListT = std::tuple<Policies...>; using CallInfoT = call::info_for_signature_t<Signature>; using ReturnT = typename Expectation<Signature>::ReturnT; template <typename TimesArg, typename FinalizerArg, typename... PolicyArgs> requires std::constructible_from<TimesT, TimesArg> && std::constructible_from<FinalizerT, FinalizerArg> && std::constructible_from<PolicyListT, PolicyArgs...> constexpr explicit BasicExpectation( const std::source_location& sourceLocation, TimesArg&& timesArg, FinalizerArg&& finalizerArg, PolicyArgs&&... args ) noexcept( std::is_nothrow_constructible_v<TimesT, TimesArg> && std::is_nothrow_constructible_v<FinalizerT, FinalizerArg> && (std::is_nothrow_constructible_v<Policies, PolicyArgs> && ...)) : m_SourceLocation{sourceLocation}, m_Policies{std::forward<PolicyArgs>(args)...}, m_Times{std::forward<TimesArg>(timesArg)}, m_Finalizer{std::forward<FinalizerArg>(finalizerArg)} { } [[nodiscard]] ExpectationReport report() const override { return ExpectationReport{ .sourceLocation = m_SourceLocation, .finalizerDescription = std::nullopt, .timesDescription = m_Times.describe_state(), .expectationDescriptions = std::apply( [&](const auto&... policies) { return std::vector<std::optional<StringT>>{ policies.describe()... }; }, m_Policies) }; } [[nodiscard]] constexpr bool is_satisfied() const noexcept override { return m_Times.is_satisfied() && std::apply( [](const auto&... policies) noexcept { return (... && policies.is_satisfied()); }, m_Policies); } [[nodiscard]] MatchReport matches(const CallInfoT& call) const override { return MatchReport{ .sourceLocation = m_SourceLocation, .finalizeReport = {std::nullopt}, .timesReport = MatchReport::Times{ .isApplicable = m_Times.is_applicable(), .description = m_Times.describe_state() }, .expectationReports = std::apply( [&](const auto&... policies) { return std::vector<MatchReport::Expectation>{ MatchReport::Expectation{ .isMatching = policies.matches(call), .description = policies.describe() }... }; }, m_Policies) }; } constexpr void consume(const CallInfoT& call) override { m_Times.consume(); std::apply( [&](auto&... policies) noexcept { (..., policies.consume(call)); }, m_Policies); } [[nodiscard]] constexpr ReturnT finalize_call(const CallInfoT& call) override { return m_Finalizer.finalize_call(call); } [[nodiscard]] constexpr const std::source_location& from() const noexcept { return m_SourceLocation; } private: std::source_location m_SourceLocation; PolicyListT m_Policies; [[no_unique_address]] TimesT m_Times{}; [[no_unique_address]] FinalizerT m_Finalizer{}; }; template <typename Signature> class ScopedExpectation { public: using StorageT = ExpectationCollection<Signature>; using ExpectationT = Expectation<Signature>; ~ScopedExpectation() noexcept(false) { if (m_Storage && m_Expectation) { m_Storage->remove(m_Expectation); } } [[nodiscard]] explicit ScopedExpectation( std::shared_ptr<StorageT> storage, std::shared_ptr<ExpectationT> expectation ) noexcept : m_Storage{std::move(storage)}, m_Expectation{std::move(expectation)} { assert(m_Storage && "Storage is nullptr."); assert(m_Expectation && "Expectation is nullptr."); m_Storage->push(m_Expectation); } template <typename T> requires requires(const std::source_location& loc) { { std::declval<T&&>().finalize(loc) } -> std::convertible_to<ScopedExpectation>; } [[nodiscard]] explicit(false) constexpr ScopedExpectation(T&& object, const std::source_location& loc = std::source_location::current()) : ScopedExpectation{std::forward<T>(object).finalize(loc)} { } ScopedExpectation(const ScopedExpectation&) = delete; ScopedExpectation& operator =(const ScopedExpectation&) = delete; [[nodiscard]] ScopedExpectation(ScopedExpectation&&) = default; ScopedExpectation& operator =(ScopedExpectation&&) = default; [[nodiscard]] bool is_satisfied() const { if (m_Expectation) { return m_Expectation->is_satisfied(); } throw std::runtime_error{"Expired expectation."}; } [[nodiscard]] const ExpectationT& expectation() const noexcept { return *m_Expectation; } private: std::shared_ptr<StorageT> m_Storage{}; std::shared_ptr<ExpectationT> m_Expectation{}; }; } #endif /*** End of inlined file: Expectation.hpp ***/ #include <cassert> #include <functional> namespace mimicpp::expectation_policies::detail { [[nodiscard]] inline StringT describe_times_state(const std::size_t current, const std::size_t min, const std::size_t max) { const auto verbalizeValue = [](const std::size_t value)-> StringT { switch (value) { case 0: return "never"; case 1: return "once"; case 2: return "twice"; default: return format::format("{} times", value); } }; if (current == max) { return format::format( "inapplicable: already saturated (matched {})", verbalizeValue(current)); } if (min <= current) { return format::format( "applicable: accepts further matches (matched {} out of {} times)", current, max); } const auto verbalizeInterval = [verbalizeValue](const std::size_t start, const std::size_t end) { if (start < end) { return format::format( "between {} and {} times", start, end); } return format::format( "exactly {}", verbalizeValue(end)); }; return format::format( "unsatisfied: matched {} - {} is expected", verbalizeValue(current), verbalizeInterval(min, max)); } } namespace mimicpp::expectation_policies { class InitFinalize { public: template <typename Return, typename... Args> static constexpr void finalize_call(const call::Info<Return, Args...>&) noexcept { } }; template <std::size_t min, std::size_t max> requires (min <= max) class Times { public: [[nodiscard]] constexpr bool is_satisfied() const noexcept { return min <= m_Count && m_Count <= max; } [[nodiscard]] constexpr bool is_applicable() const noexcept { return m_Count < max; } [[nodiscard]] StringT describe_state() const { return detail::describe_times_state( m_Count, min, max); } constexpr void consume() noexcept { ++m_Count; } private: std::size_t m_Count{}; }; class InitTimes : public Times<1u, 1u> { }; class RuntimeTimes { public: [[nodiscard]] constexpr explicit RuntimeTimes(const std::size_t min, const std::size_t max) : m_Min{min}, m_Max{max} { if (m_Max < m_Min) { throw std::runtime_error{"min must be less or equal to max."}; } } [[nodiscard]] constexpr bool is_satisfied() const noexcept { return m_Min <= m_Count && m_Count <= m_Max; } [[nodiscard]] constexpr bool is_applicable() const noexcept { return m_Count < m_Max; } [[nodiscard]] StringT describe_state() const { return detail::describe_times_state( m_Count, m_Min, m_Max); } constexpr void consume() noexcept { ++m_Count; } private: std::size_t m_Min; std::size_t m_Max; std::size_t m_Count{}; }; template <ValueCategory expected> class Category { public: static constexpr bool is_satisfied() noexcept { return true; } template <typename Return, typename... Args> static constexpr bool matches(const call::Info<Return, Args...>& info) noexcept { return mimicpp::is_matching(info.fromCategory, expected); } template <typename Return, typename... Args> static constexpr void consume(const call::Info<Return, Args...>& info) noexcept { assert(mimicpp::is_matching(info.fromCategory, expected) && "Call does not match."); } [[nodiscard]] static StringT describe() { return format::format( "expect: from {} category overload", expected); } }; template <Constness constness> class Constness { public: static constexpr bool is_satisfied() noexcept { return true; } template <typename Return, typename... Args> static constexpr bool matches(const call::Info<Return, Args...>& info) noexcept { return mimicpp::is_matching(info.fromConstness, constness); } template <typename Return, typename... Args> static constexpr void consume(const call::Info<Return, Args...>& info) noexcept { assert(mimicpp::is_matching(info.fromConstness, constness) && "Call does not match."); } [[nodiscard]] static StringT describe() { return format::format( "expect: from {} qualified overload", constness); } }; template <typename Action> requires std::same_as<Action, std::remove_cvref_t<Action>> && std::is_move_constructible_v<Action> class ReturnsResultOf { public: [[nodiscard]] explicit constexpr ReturnsResultOf( Action&& action ) noexcept(std::is_nothrow_move_constructible_v<Action>) : m_Action{std::move(action)} { } template <typename Return, typename... Args> requires std::invocable<Action&, const call::Info<Return, Args...>&> && explicitly_convertible_to< std::invoke_result_t<Action&, const call::Info<Return, Args...>&>, Return> [[nodiscard]] constexpr Return finalize_call( [[maybe_unused]] const call::Info<Return, Args...>& call ) noexcept( std::is_nothrow_invocable_v<Action&, const call::Info<Return, Args...>&> && nothrow_explicitly_convertible_to< std::invoke_result_t<Action&, const call::Info<Return, Args...>&>, Return>) { return static_cast<Return>( std::invoke(m_Action, call)); } private: Action m_Action; }; template <typename Exception> requires (!std::is_reference_v<Exception>) && std::copyable<Exception> class Throws { public: [[nodiscard]] explicit constexpr Throws(Exception exception) noexcept(std::is_nothrow_move_constructible_v<Exception>) : m_Exception{std::move(exception)} { } template <typename Return, typename... Args> constexpr Return finalize_call( [[maybe_unused]] const call::Info<Return, Args...>& call ) { throw m_Exception; // NOLINT(hicpp-exception-baseclass) } private: Exception m_Exception; }; template <typename Matcher, typename Projection, typename Describer> requires std::same_as<Matcher, std::remove_cvref_t<Matcher>> && std::same_as<Projection, std::remove_cvref_t<Projection>> && std::same_as<Describer, std::remove_cvref_t<Describer>> && std::is_move_constructible_v<Matcher> && std::is_move_constructible_v<Projection> && std::is_move_constructible_v<Describer> class Requirement { public: [[nodiscard]] explicit constexpr Requirement( Matcher matcher, Projection projection = Projection{}, Describer describer = Describer{} ) noexcept( std::is_nothrow_move_constructible_v<Matcher> && std::is_nothrow_move_constructible_v<Projection> && std::is_nothrow_move_constructible_v<Describer>) : m_Matcher{std::move(matcher)}, m_Projection{std::move(projection)}, m_Describer{std::move(describer)} { } static constexpr bool is_satisfied() noexcept { return true; } template <typename Return, typename... Args> requires std::invocable<const Projection&, const call::Info<Return, Args...>&> && matcher_for< Matcher, std::invoke_result_t<const Projection&, const call::Info<Return, Args...>&>> [[nodiscard]] constexpr bool matches(const call::Info<Return, Args...>& info) const { return m_Matcher.matches( std::invoke(m_Projection, info)); } template <typename Return, typename... Args> static constexpr void consume([[maybe_unused]] const call::Info<Return, Args...>& info) noexcept { } [[nodiscard]] std::optional<StringT> describe() const { if (const std::optional<StringT> description = m_Matcher.describe()) { return std::invoke( m_Describer, *description); } return std::nullopt; } private: [[no_unique_address]] Matcher m_Matcher; [[no_unique_address]] Projection m_Projection; [[no_unique_address]] Describer m_Describer; }; template <typename Action> class SideEffectAction { public: ~SideEffectAction() = default; [[nodiscard]] explicit constexpr SideEffectAction( Action&& action ) noexcept(std::is_nothrow_move_constructible_v<Action>) : m_Action{std::move(action)} { } SideEffectAction(const SideEffectAction&) = delete; SideEffectAction& operator =(const SideEffectAction&) = delete; [[nodiscard]] SideEffectAction(SideEffectAction&&) = default; SideEffectAction& operator =(SideEffectAction&&) = default; static constexpr bool is_satisfied() noexcept { return true; } template <typename Return, typename... Args> [[nodiscard]] static constexpr bool matches(const call::Info<Return, Args...>&) noexcept { return true; } [[nodiscard]] static std::nullopt_t describe() noexcept { return std::nullopt; } template <typename Return, typename... Args> requires std::invocable<Action&, const call::Info<Return, Args...>&> constexpr void consume( const call::Info<Return, Args...>& info ) noexcept(std::is_nothrow_invocable_v<Action&, const call::Info<Return, Args...>&>) { std::invoke(m_Action, info); } private: Action m_Action; }; template <typename Action, template <typename> typename Projection> requires std::same_as<Action, std::remove_cvref_t<Action>> class ApplyAllArgsAction { public: [[nodiscard]] explicit constexpr ApplyAllArgsAction( Action action = Action{} ) noexcept(std::is_nothrow_move_constructible_v<Action>) : m_Action{std::move(action)} { } template <typename Arg> using ProjectedArgT = Projection<Arg>; template <typename Return, typename... Args> requires std::invocable< const Action&, ProjectedArgT<Args>...> constexpr decltype(auto) operator ()( const call::Info<Return, Args...>& callInfo ) const noexcept( std::is_nothrow_invocable_v< const Action&, ProjectedArgT<Args>...>) { static_assert( (... && explicitly_convertible_to<Args&, ProjectedArgT<Args>>), "Projection can not be applied."); return std::apply( [this](auto&... args) -> decltype(auto) { return std::invoke( m_Action, static_cast<ProjectedArgT<Args>>( args.get())...); }, callInfo.args); } private: Action m_Action; }; template <typename Action, template <typename> typename Projection, std::size_t... indices> requires std::same_as<Action, std::remove_cvref_t<Action>> class ApplyArgsAction { public: [[nodiscard]] explicit constexpr ApplyArgsAction( Action action = Action{} ) noexcept(std::is_nothrow_move_constructible_v<Action>) : m_Action{std::move(action)} { } template <std::size_t index, typename... Args> using ArgListElementT = std::tuple_element_t<index, std::tuple<Args...>>; template <std::size_t index, typename... Args> using ProjectedArgListElementT = Projection<ArgListElementT<index, Args...>>; template <typename Return, typename... Args> requires (... && (indices < sizeof...(Args))) && std::invocable< const Action&, ProjectedArgListElementT<indices, Args...>...> constexpr decltype(auto) operator ()( const call::Info<Return, Args...>& callInfo ) const noexcept( std::is_nothrow_invocable_v< const Action&, ProjectedArgListElementT<indices, Args...>...>) { static_assert( (explicitly_convertible_to< ArgListElementT<indices, Args...>&, ProjectedArgListElementT<indices, Args...>> && ...), "Projection can not be applied."); return std::invoke( m_Action, static_cast<ProjectedArgListElementT<indices, Args...>>( std::get<indices>(callInfo.args).get())...); } private: Action m_Action; }; } namespace mimicpp::expectation_policies::detail { struct forward_fn { template <typename T> [[nodiscard]] constexpr T&& operator ()(T&& obj) const noexcept { return std::forward<T>(obj); } }; } namespace mimicpp::expect { template <std::size_t min, std::size_t max = min> [[nodiscard]] consteval expectation_policies::Times<min, max> times() noexcept { return {}; } template <std::size_t min> [[nodiscard]] consteval expectation_policies::Times<min, std::numeric_limits<std::size_t>::max()> at_least() noexcept { return {}; } template <std::size_t max> [[nodiscard]] consteval expectation_policies::Times<0u, max> at_most() noexcept { return {}; } [[nodiscard]] consteval expectation_policies::Times<1u, 1u> once() noexcept { return {}; } [[nodiscard]] consteval expectation_policies::Times<2u, 2u> twice() noexcept { return {}; } [[nodiscard]] constexpr expectation_policies::RuntimeTimes times(const std::size_t min, const std::size_t max) { return expectation_policies::RuntimeTimes{min, max}; } [[nodiscard]] constexpr expectation_policies::RuntimeTimes times(const std::size_t exactly) noexcept { return times(exactly, exactly); } [[nodiscard]] constexpr expectation_policies::RuntimeTimes at_least(const std::size_t min) noexcept { return expectation_policies::RuntimeTimes{min, std::numeric_limits<std::size_t>::max()}; } [[nodiscard]] constexpr expectation_policies::RuntimeTimes at_most(const std::size_t max) noexcept { return expectation_policies::RuntimeTimes{0u, max}; } namespace detail { template <std::size_t index> struct arg_requirement_describer { [[nodiscard]] constexpr StringT operator ()( const StringViewT matcherDescription ) const { return format::format( "expect: arg[{}] {}", index, matcherDescription); } }; } /** * \defgroup EXPECTATION_REQUIREMENT requirement * \ingroup EXPECTATION * \brief Requirements determine, whether an expectation matches an incoming call. * \details Requirements are the building blocks, which determine whether a call satisfies the expectation. If any of the specified * requirements fail, there is no match. * \note An expectation without requirements matches any call. * * \details Requirements are checked during the ``matches`` step. If all requirements match, an additional ``is_applicable`` check is * performed on the times requirements. If this returns ``false``, the expectation is treated as ``inapplicable`` and will be skipped * (but reported if no other match can be found). Otherwise, the call is matched. * *\{ */ /** * \brief Checks, whether the selected argument matches the given matcher. * \tparam Matcher The matcher type. * \param matcher The matcher. * * \details This requirement checks, whether the selected argument matches the given matcher. One argument can be checked multiple times * in different requirements and all results will be combined as conjunction. * * For a list of built-in matchers, see \ref EXPECTATION_MATCHER "matcher" section. * \snippet Requirements.cpp expect::arg */ template <std::size_t index, typename Matcher> [[nodiscard]] constexpr auto arg( Matcher&& matcher ) noexcept(std::is_nothrow_constructible_v<std::remove_cvref_t<Matcher>, Matcher&&>) { return expectation_policies::Requirement< std::remove_cvref_t<Matcher>, expectation_policies::ApplyArgsAction< expectation_policies::detail::forward_fn, std::add_lvalue_reference_t, index>, detail::arg_requirement_describer<index>>{ std::forward<Matcher>(matcher), }; } /** * \} */ } namespace mimicpp::finally { // ReSharper disable CppDoxygenUnresolvedReference /** * \defgroup EXPECTATION_FINALIZER finalizer * \ingroup EXPECTATION * \brief Finalizers are the last step of a matching expectation. * \details Finalizers are executed for calls, which have a matching expectation. They are responsible for either returning an * appropriate return value or leaving the call by throwing an exception. * * An expectation must have exactly one finalizer applied. For mocks returning void, an appropriate finalizer is attached by default. * This default finalizer can be exchanged once. If an expectation setup contains multiple finalize statements, a compile error is * triggered. * *# Custom Finalizers * There are several provided finalizers, but user may create their own as desired. * A valid finalizer has to satisfy the ``finalize_policy_for`` concept, which in fact requires the existence of a ``finalize_call`` * member function. * \snippet Finalizers.cpp custom finalizer *\{ */ /** * \brief During the finalization step, the invocation result of the given function is returned. * \snippet Finalizers.cpp finally::returns_result_of * \tparam Fun The function type. * \param fun The function to be invoked. * \return Forward returns the invocation result of ``fun``. * * \details The provided functions must be invocable without arguments, but may return any type, which is explicitly convertible to * the mocks return type. */ template <typename Fun> requires std::invocable<std::remove_cvref_t<Fun>&> && (!std::is_void_v<std::invoke_result_t<std::remove_cvref_t<Fun>&>>) [[nodiscard]] constexpr auto returns_result_of( Fun&& fun // NOLINT(cppcoreguidelines-missing-std-forward) ) noexcept(std::is_nothrow_constructible_v<std::remove_cvref_t<Fun>, Fun>) { return expectation_policies::ReturnsResultOf{ [ fun = std::forward<Fun>(fun) ]([[maybe_unused]] const auto& call) mutable noexcept(std::is_nothrow_invocable_v<decltype(fun)>) -> decltype(auto) { return std::invoke(fun); } }; } /** * \brief During the finalization step, the stored value is returned. * \tparam T The value type. * \param value The value to be returned. * \return Returns a copy of ``value``. * * \details The provided value must be copyable, because multiple invocations must be possible. * \snippet Finalizers.cpp finally::returns * * This finalizer is aware of ``std::reference_wrapper``, which can be used to return values from the outer scope. Such values are * explicitly unwrapped, before they are returned. * \snippet Finalizers.cpp finally::returns std::ref * * In fact any value category can be returned; it is the users responsibility to make sure, not to use any dangling references. * If an actual value is stored and a reference is returned, this is fine until the expectation goes out of scope. * \snippet Finalizers.cpp finally::returns ref */ template <typename T> requires std::copyable<std::remove_cvref_t<T>> [[nodiscard]] constexpr auto returns( T&& value // NOLINT(cppcoreguidelines-missing-std-forward) ) noexcept(std::is_nothrow_constructible_v<std::remove_cvref_t<T>, T>) { return expectation_policies::ReturnsResultOf{ [v = std::forward<T>(value)]([[maybe_unused]] const auto& call) mutable noexcept -> auto& { return static_cast<std::unwrap_reference_t<decltype(v)>&>(v); } }; } /** * \brief During the finalization step, the selected call arguments are applied on the given action. * \tparam index The first selected argument index. * \tparam otherIndices Addition selected arguments. * \tparam Action The action type. * \param action The action to be applied to. * \return Returns the invocation result of the given action. * * \details The selected call arguments are applied as (possibly const qualified) lvalue-references. The action may proceed with them * as desired, but be aware that this may actually affect objects outside the call (e.g. if call arguments are lvalues.). * \snippet Finalizers.cpp finally::returns_apply_result_of */ template <std::size_t index, std::size_t... otherIndices, typename Action> [[nodiscard]] constexpr auto returns_apply_result_of( Action&& action ) noexcept(std::is_nothrow_constructible_v<std::remove_cvref_t<Action>, Action>) { return expectation_policies::ReturnsResultOf{ expectation_policies::ApplyArgsAction< std::remove_cvref_t<Action>, std::add_lvalue_reference_t, index, otherIndices...>{ std::forward<Action>(action) } }; } /** * \brief During the finalization step, all call arguments are applied on the given action. * \tparam Action The action type. * \param action The action to be applied to. * \return Returns the invocation result of the given action. * * \details All call arguments are applied as (possibly const qualified) lvalue-references. The action may proceed with them * as desired, but be aware that this may actually affect objects outside the call (e.g. if call arguments are lvalues.). * \snippet Finalizers.cpp finally::returns_apply_all_result_of */ template <typename Action> [[nodiscard]] constexpr auto returns_apply_all_result_of( Action&& action ) noexcept(std::is_nothrow_constructible_v<std::remove_cvref_t<Action>, Action>) { return expectation_policies::ReturnsResultOf{ expectation_policies::ApplyAllArgsAction< std::remove_cvref_t<Action>, std::add_lvalue_reference_t>{ std::forward<Action>(action) } }; } /** * \brief During the finalization step, the selected call argument is returned. * \return Returns the forwarded and explicitly converted argument. * * \details The selected call argument is forwarded and explicitly converted to the mocks return type. * \snippet Finalizers.cpp finally::returns_param */ template <std::size_t index> [[nodiscard]] constexpr auto returns_arg() noexcept { return expectation_policies::ReturnsResultOf{ expectation_policies::ApplyArgsAction< expectation_policies::detail::forward_fn, std::add_rvalue_reference_t, index>{ expectation_policies::detail::forward_fn{} } }; } /** * \brief During the finalization step, the given exception is thrown. * \tparam T The exception type. * \param exception The exception to be thrown. * \throws A copy of ``exception``. * * \details The provided exception must be copyable, because multiple invocations must be possible. * \snippet Finalizers.cpp finally::throws */ template <typename T> requires std::copyable<std::remove_cvref_t<T>> [[nodiscard]] constexpr expectation_policies::Throws<std::remove_cvref_t<T>> throws( T&& exception ) noexcept(std::is_nothrow_constructible_v<std::remove_cvref_t<T>, T>) { return expectation_policies::Throws<std::remove_cvref_t<T>>{ std::forward<T>(exception) }; } /** * \} */ // ReSharper restore CppDoxygenUnresolvedReference } namespace mimicpp::then { /** * \defgroup EXPECTATION_SIDE_EFFECTS side effects * \ingroup EXPECTATION * \brief Side effects are a convenient way to apply actions on matched expectations. * \details After a match has been created, side effects will be applied during the ``consume`` step. * They may alter the call arguments and capture any variable from the outside scope. Beware that those captured variables * must outlive the expectation they are attached on. * * Side effects will be executed in order of their construction and later side effects will observe any changes applied on * arguments by prior side effects. * * \attention Side effects should never throw. If it is actually intended to throw an exception as a result, use * ``finally::throws`` instead. * * As side effects actually are ``expectation policies``, they may execute special behavior during ``is_satisfied`` and * ``matches`` steps. That being said, the provided side effects are actually no-ops on these functions, but custom side * effects may behave differently. * *\{ */ /** * \brief Applies the argument at the specified index on the given action. * \tparam index The argument index. * \tparam Action The action type. * \param action The action to be applied. * \return Newly created side effect action. */ template <std::size_t index, typename Action> [[nodiscard]] constexpr auto apply_arg( Action&& action ) noexcept(std::is_nothrow_constructible_v<std::remove_cvref_t<Action>, Action>) { return expectation_policies::SideEffectAction{ expectation_policies::ApplyArgsAction< std::remove_cvref_t<Action>, std::add_lvalue_reference_t, index>{ std::forward<Action>(action) } }; } /** * \brief Applies the arguments at the specified index and in that order on the given action. * \details This functions creates a side effect policy and applies the selected arguments in the specified order. * The indices can be in any order and may also contain duplicates. * \tparam index The first argument index. * \tparam additionalIndices Additional argument indices. * \tparam Action The action type. * \param action The action to be applied. * \return Newly created side effect action. */ template <std::size_t index, std::size_t... additionalIndices, typename Action> [[nodiscard]] constexpr auto apply_args( Action&& action ) noexcept(std::is_nothrow_constructible_v<std::remove_cvref_t<Action>, Action>) { return expectation_policies::SideEffectAction{ expectation_policies::ApplyArgsAction< std::remove_cvref_t<Action>, std::add_lvalue_reference_t, index, additionalIndices...>{ std::forward<Action>(action) } }; } /** * \brief Applies all arguments on the given action. * \tparam Action The action type. * \param action The action to be applied. * \return Newly created side effect action. */ template <typename Action> [[nodiscard]] constexpr auto apply_all( Action&& action ) noexcept(std::is_nothrow_constructible_v<std::remove_cvref_t<Action>, Action>) { return expectation_policies::SideEffectAction{ expectation_policies::ApplyAllArgsAction< std::remove_cvref_t<Action>, std::add_lvalue_reference_t>{ std::forward<Action>(action) } }; } /** * \brief Invokes the given function. * \tparam Action The action type. * \param action The action to be invoked. * \return Newly created side effect action. */ template <std::invocable Action> [[nodiscard]] constexpr auto invoke( Action&& action // NOLINT(cppcoreguidelines-missing-std-forward) ) noexcept(std::is_nothrow_constructible_v<std::remove_cvref_t<Action>, Action>) { return expectation_policies::SideEffectAction{ [ action = std::forward<Action>(action) ]([[maybe_unused]] const auto& call) mutable noexcept(std::is_nothrow_invocable_v<Action&>) { std::invoke(action); } }; } /** * \} */ } #endif /*** End of inlined file: ExpectationPolicies.hpp ***/ /*** Start of inlined file: ExpectationBuilder.hpp ***/ // // Copyright Dominic (DNKpp) Koepke 2024 - 2024. // // Distributed under the Boost Software License, Version 1.0. // // (See accompanying file LICENSE_1_0.txt or copy at // // https://www.boost.org/LICENSE_1_0.txt) #ifndef MIMICPP_EXPECTATION_BUILDER_HPP #define MIMICPP_EXPECTATION_BUILDER_HPP #pragma once namespace mimicpp { template < typename Signature, times_policy TimesPolicy, typename FinalizePolicy, expectation_policy_for<Signature>... Policies> class BasicExpectationBuilder { public: using StorageT = ExpectationCollection<Signature>; using ScopedExpectationT = ScopedExpectation<Signature>; using PolicyListT = std::tuple<Policies...>; using ReturnT = typename Expectation<Signature>::ReturnT; ~BasicExpectationBuilder() = default; template <typename TimesPolicyArg, typename FinalizePolicyArg, typename PolicyListArg> requires std::constructible_from<TimesPolicy, TimesPolicyArg> && std::constructible_from<FinalizePolicy, FinalizePolicyArg> && std::constructible_from<PolicyListT, PolicyListArg> [[nodiscard]] explicit constexpr BasicExpectationBuilder( std::shared_ptr<StorageT> storage, TimesPolicyArg&& timesPolicyArg, FinalizePolicyArg&& finalizePolicyArg, PolicyListArg&& policyListArg ) noexcept : m_Storage{std::move(storage)}, m_TimesPolicy{std::forward<TimesPolicyArg>(timesPolicyArg)}, m_FinalizePolicy{std::forward<FinalizePolicyArg>(finalizePolicyArg)}, m_ExpectationPolicies{std::forward<PolicyListArg>(policyListArg)} { assert(m_Storage && "Storage is nullptr."); } BasicExpectationBuilder(const BasicExpectationBuilder&) = delete; BasicExpectationBuilder& operator =(const BasicExpectationBuilder&) = delete; [[nodiscard]] BasicExpectationBuilder(BasicExpectationBuilder&&) = default; BasicExpectationBuilder& operator =(BasicExpectationBuilder&&) = default; template <typename Policy> requires std::same_as<expectation_policies::InitTimes, TimesPolicy> && (!std::same_as<expectation_policies::InitTimes, std::remove_cvref_t<Policy>>) && times_policy<std::remove_cvref_t<Policy>> [[nodiscard]] constexpr auto operator |(Policy&& policy) && { using ExtendedExpectationBuilderT = BasicExpectationBuilder< Signature, std::remove_cvref_t<Policy>, FinalizePolicy, Policies...>; return ExtendedExpectationBuilderT{ std::move(m_Storage), std::forward<Policy>(policy), std::move(m_FinalizePolicy), std::move(m_ExpectationPolicies) }; } template <typename Policy> requires std::same_as<expectation_policies::InitFinalize, FinalizePolicy> && (!std::same_as<expectation_policies::InitFinalize, std::remove_cvref_t<Policy>>) && finalize_policy_for<std::remove_cvref_t<Policy>, Signature> [[nodiscard]] constexpr auto operator |(Policy&& policy) && { using ExtendedExpectationBuilderT = BasicExpectationBuilder< Signature, TimesPolicy, std::remove_cvref_t<Policy>, Policies...>; return ExtendedExpectationBuilderT{ std::move(m_Storage), std::move(m_TimesPolicy), std::forward<Policy>(policy), std::move(m_ExpectationPolicies) }; } template <typename Policy> requires expectation_policy_for<std::remove_cvref_t<Policy>, Signature> [[nodiscard]] constexpr auto operator |(Policy&& policy) && { using ExtendedExpectationBuilderT = BasicExpectationBuilder< Signature, TimesPolicy, FinalizePolicy, Policies..., std::remove_cvref_t<Policy>>; return ExtendedExpectationBuilderT{ std::move(m_Storage), std::move(m_TimesPolicy), std::move(m_FinalizePolicy), std::apply( [&](auto&... policies) noexcept { return std::forward_as_tuple( std::move(policies)..., std::forward<Policy>(policy)); }, m_ExpectationPolicies) }; } [[nodiscard]] constexpr ScopedExpectationT finalize(const std::source_location& sourceLocation) && { static_assert( finalize_policy_for<FinalizePolicy, Signature>, "For non-void return types, a finalize policy must be set."); using ExpectationT = BasicExpectation<Signature, TimesPolicy, FinalizePolicy, Policies...>; return ScopedExpectationT{ std::move(m_Storage), std::apply( [&](auto&... policies) { return std::make_unique<ExpectationT>( sourceLocation, std::move(m_TimesPolicy), std::move(m_FinalizePolicy), std::move(policies)...); }, m_ExpectationPolicies) }; } private: std::shared_ptr<StorageT> m_Storage; TimesPolicy m_TimesPolicy{}; FinalizePolicy m_FinalizePolicy{}; PolicyListT m_ExpectationPolicies{}; }; template <typename Signature, typename... Policies> ScopedExpectation(BasicExpectationBuilder<Signature, Policies...>&&) -> ScopedExpectation<Signature>; } namespace mimicpp::detail { template <typename Signature, std::size_t index, typename Arg> requires matcher_for< std::remove_cvref_t<Arg>, signature_param_type_t<index, Signature>> [[nodiscard]] constexpr auto make_arg_policy(Arg&& arg, [[maybe_unused]] const priority_tag<2>) { return expect::arg<index>(std::forward<Arg>(arg)); } template <typename Signature, std::size_t index, std::equality_comparable_with<signature_param_type_t<index, Signature>> Arg> [[nodiscard]] constexpr auto make_arg_policy(Arg&& arg, [[maybe_unused]] const priority_tag<1>) { return expect::arg<index>( matches::eq(std::forward<Arg>(arg))); } template <typename Signature, std::size_t index, typename Arg> constexpr void make_arg_policy([[maybe_unused]] Arg&& arg, [[maybe_unused]] const priority_tag<0>) noexcept // NOLINT(cppcoreguidelines-missing-std-forward) { static_assert( always_false<Arg>{}, "The provided argument is neither a matcher, nor is it equality comparable with the selected param."); } template <typename Signature, typename Builder, std::size_t... indices, typename... Args> [[nodiscard]] constexpr auto extend_builder_with_arg_policies( Builder&& builder, const std::index_sequence<indices...>, Args&&... args ) { return ( std::forward<Builder>(builder) | ... | detail::make_arg_policy<Signature, indices>( std::forward<Args>(args), priority_tag<2>{})); } template <typename Signature, typename... Args> constexpr auto make_expectation_builder( std::shared_ptr<ExpectationCollection<Signature>> expectations, Args&&... args ) { using BaseBuilderT = BasicExpectationBuilder< Signature, expectation_policies::InitTimes, expectation_policies::InitFinalize >; return detail::extend_builder_with_arg_policies<Signature>( BaseBuilderT{ std::move(expectations), expectation_policies::InitTimes{}, expectation_policies::InitFinalize{}, std::tuple{} }, std::index_sequence_for<Args...>{}, std::forward<Args>(args)...); } } #define MIMICPP_UNIQUE_NAME(prefix, counter) prefix##counter #define MIMICPP_SCOPED_EXPECTATION_IMPL(counter) \ [[maybe_unused]] \ const ::mimicpp::ScopedExpectation MIMICPP_UNIQUE_NAME(_mimicpp_expectation_, counter) = #define MIMICPP_SCOPED_EXPECTATION MIMICPP_SCOPED_EXPECTATION_IMPL(__COUNTER__) #define SCOPED_EXP MIMICPP_SCOPED_EXPECTATION #endif /*** End of inlined file: ExpectationBuilder.hpp ***/ /*** Start of inlined file: Mock.hpp ***/ // // Copyright Dominic (DNKpp) Koepke 2024 - 2024. // // Distributed under the Boost Software License, Version 1.0. // // (See accompanying file LICENSE_1_0.txt or copy at // // https://www.boost.org/LICENSE_1_0.txt) #ifndef MIMICPP_MOCK_HPP #define MIMICPP_MOCK_HPP #pragma once namespace mimicpp::detail { template <typename T, typename Target> concept requirement_for = std::equality_comparable_with<T, Target> || matcher_for<std::remove_cvref_t<T>, Target>; template <ValueCategory category, Constness constness, typename Return, typename... Params> class BasicMockFrontend { protected: using ExpectationCollectionT = ExpectationCollection<Return(Params...)>; [[nodiscard]] explicit BasicMockFrontend( std::shared_ptr<ExpectationCollectionT> collection = std::make_shared<ExpectationCollectionT>() ) noexcept : m_Expectations{std::move(collection)} { } constexpr Return handle_call( std::tuple<std::reference_wrapper<std::remove_reference_t<Params>>...> params, const std::source_location& from ) const { using CallInfoT = call::Info<Return, Params...>; return m_Expectations->handle_call( CallInfoT{ .args = std::move(params), .fromCategory = category, .fromConstness = constness, .fromSourceLocation = from }); } template <typename... Args> requires (... && requirement_for<Args, Params>) [[nodiscard]] constexpr auto make_expectation_builder(Args&&... args) const { return detail::make_expectation_builder( m_Expectations, std::forward<Args>(args)...) | expectation_policies::Category<category>{} | expectation_policies::Constness<constness>{}; } private: std::shared_ptr<ExpectationCollectionT> m_Expectations; }; template <typename Signature> class MockFrontend; template <typename Return, typename... Params> class MockFrontend<Return(Params...)> : private BasicMockFrontend< ValueCategory::any, Constness::non_const, Return, Params...> { using SuperT = BasicMockFrontend< ValueCategory::any, Constness::non_const, Return, Params...>; public: using SignatureT = Return(Params...); constexpr Return operator ()(Params... params, const std::source_location& from = std::source_location::current()) { return SuperT::handle_call( std::tuple{std::ref(params)...}, from); } template <typename... Args> requires (... && requirement_for<Args, Params>) [[nodiscard]] constexpr auto expect_call(Args&&... args) { return SuperT::make_expectation_builder( std::forward<Args>(args)...); } }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) const> : public BasicMockFrontend< ValueCategory::any, Constness::as_const, Return, Params...> { using SuperT = BasicMockFrontend< ValueCategory::any, Constness::as_const, Return, Params...>; public: using SignatureT = Return(Params...) const; constexpr Return operator ()(Params... params, const std::source_location& from = std::source_location::current()) const { return SuperT::handle_call( std::tuple{std::ref(params)...}, from); } template <typename... Args> requires (... && requirement_for<Args, Params>) [[nodiscard]] constexpr auto expect_call(Args&&... args) const { return SuperT::make_expectation_builder( std::forward<Args>(args)...); } }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) &> : private BasicMockFrontend< ValueCategory::lvalue, Constness::non_const, Return, Params...> { using SuperT = BasicMockFrontend< ValueCategory::lvalue, Constness::non_const, Return, Params...>; public: using SignatureT = Return(Params...) &; constexpr Return operator ()(Params... params, const std::source_location& from = std::source_location::current()) & { return SuperT::handle_call( std::tuple{std::ref(params)...}, from); } template <typename... Args> requires (... && requirement_for<Args, Params>) [[nodiscard]] constexpr auto expect_call(Args&&... args) & { return SuperT::make_expectation_builder( std::forward<Args>(args)...); } }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) const &> : public BasicMockFrontend< ValueCategory::lvalue, Constness::as_const, Return, Params...> { using SuperT = BasicMockFrontend< ValueCategory::lvalue, Constness::as_const, Return, Params...>; public: using SignatureT = Return(Params...) const &; constexpr Return operator ()(Params... params, const std::source_location& from = std::source_location::current()) const & { return SuperT::handle_call( std::tuple{std::ref(params)...}, from); } template <typename... Args> requires (... && requirement_for<Args, Params>) [[nodiscard]] constexpr auto expect_call(Args&&... args) const & { return SuperT::make_expectation_builder( std::forward<Args>(args)...); } }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) &&> : private BasicMockFrontend< ValueCategory::rvalue, Constness::non_const, Return, Params...> { using SuperT = BasicMockFrontend< ValueCategory::rvalue, Constness::non_const, Return, Params...>; public: using SignatureT = Return(Params...) &&; constexpr Return operator ()(Params... params, const std::source_location& from = std::source_location::current()) && { return SuperT::handle_call( std::tuple{std::ref(params)...}, from); } template <typename... Args> requires (... && requirement_for<Args, Params>) [[nodiscard]] constexpr auto expect_call(Args&&... args) && { return SuperT::make_expectation_builder( std::forward<Args>(args)...); } }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) const &&> : public BasicMockFrontend< ValueCategory::rvalue, Constness::as_const, Return, Params...> { using SuperT = BasicMockFrontend< ValueCategory::rvalue, Constness::as_const, Return, Params...>; public: using SignatureT = Return(Params...) const &&; constexpr Return operator ()(Params... params, const std::source_location& from = std::source_location::current()) const && { return SuperT::handle_call( std::tuple{std::ref(params)...}, from); } template <typename... Args> requires (... && requirement_for<Args, Params>) [[nodiscard]] constexpr auto expect_call(Args&&... args) const && { return SuperT::make_expectation_builder( std::forward<Args>(args)...); } }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) noexcept> : private BasicMockFrontend< ValueCategory::any, Constness::non_const, Return, Params...> { using SuperT = BasicMockFrontend< ValueCategory::any, Constness::non_const, Return, Params...>; public: using SignatureT = Return(Params...) noexcept; constexpr Return operator ()(Params... params, const std::source_location& from = std::source_location::current()) noexcept { return SuperT::handle_call( std::tuple{std::ref(params)...}, from); } template <typename... Args> requires (... && requirement_for<Args, Params>) [[nodiscard]] constexpr auto expect_call(Args&&... args) { return SuperT::make_expectation_builder( std::forward<Args>(args)...); } }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) const noexcept> : public BasicMockFrontend< ValueCategory::any, Constness::as_const, Return, Params...> { using SuperT = BasicMockFrontend< ValueCategory::any, Constness::as_const, Return, Params...>; public: using SignatureT = Return(Params...) const noexcept; constexpr Return operator ()( Params... params, const std::source_location& from = std::source_location::current() ) const noexcept { return SuperT::handle_call( std::tuple{std::ref(params)...}, from); } template <typename... Args> requires (... && requirement_for<Args, Params>) [[nodiscard]] constexpr auto expect_call(Args&&... args) const { return SuperT::make_expectation_builder( std::forward<Args>(args)...); } }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) & noexcept> : private BasicMockFrontend< ValueCategory::lvalue, Constness::non_const, Return, Params...> { using SuperT = BasicMockFrontend< ValueCategory::lvalue, Constness::non_const, Return, Params...>; public: using SignatureT = Return(Params...) & noexcept; constexpr Return operator ()(Params... params, const std::source_location& from = std::source_location::current()) & noexcept { return SuperT::handle_call( std::tuple{std::ref(params)...}, from); } template <typename... Args> requires (... && requirement_for<Args, Params>) [[nodiscard]] constexpr auto expect_call(Args&&... args) & { return SuperT::make_expectation_builder( std::forward<Args>(args)...); } }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) const & noexcept> : public BasicMockFrontend< ValueCategory::lvalue, Constness::as_const, Return, Params...> { using SuperT = BasicMockFrontend< ValueCategory::lvalue, Constness::as_const, Return, Params...>; public: using SignatureT = Return(Params...) const & noexcept; constexpr Return operator ()( Params... params, const std::source_location& from = std::source_location::current() ) const & noexcept { return SuperT::handle_call( std::tuple{std::ref(params)...}, from); } template <typename... Args> requires (... && requirement_for<Args, Params>) [[nodiscard]] constexpr auto expect_call(Args&&... args) const & { return SuperT::make_expectation_builder( std::forward<Args>(args)...); } }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) && noexcept> : private BasicMockFrontend< ValueCategory::rvalue, Constness::non_const, Return, Params...> { using SuperT = BasicMockFrontend< ValueCategory::rvalue, Constness::non_const, Return, Params...>; public: using SignatureT = Return(Params...) && noexcept; constexpr Return operator ()(Params... params, const std::source_location& from = std::source_location::current()) && noexcept { return SuperT::handle_call( std::tuple{std::ref(params)...}, from); } template <typename... Args> requires (... && requirement_for<Args, Params>) [[nodiscard]] constexpr auto expect_call(Args&&... args) && { return SuperT::make_expectation_builder( std::forward<Args>(args)...); } }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) const && noexcept> : public BasicMockFrontend< ValueCategory::rvalue, Constness::as_const, Return, Params...> { using SuperT = BasicMockFrontend< ValueCategory::rvalue, Constness::as_const, Return, Params...>; public: using SignatureT = Return(Params...) const && noexcept; constexpr Return operator ()( Params... params, const std::source_location& from = std::source_location::current() ) const && noexcept { return SuperT::handle_call( std::tuple{std::ref(params)...}, from); } template <typename... Args> requires (... && requirement_for<Args, Params>) [[nodiscard]] constexpr auto expect_call(Args&&... args) const && { return SuperT::make_expectation_builder( std::forward<Args>(args)...); } }; } namespace mimicpp { /** * \defgroup MOCK mock * \brief The core aspect of the library. * \details Mocks are responsible for providing a convenient interface to set up expectations and handle received calls. * At a basic level users can specify arbitrary overload sets, which the mock shall provide and for which expectations can be defined. * * Mocks themselves can be used as public members and can therefore serve as member function mocks. Have a look at the following example, * which demonstrates how one is able to test a custom stack container adapter (like ``std::stack``) by utilizing mocking. * * At first, we define a simple concept, which our mock must satisfy. * \snippet Mock.cpp stack concept * The implemented must be test-able for emptiness, must have a ``push_back`` and ``pop_back`` function and must provide access to the * last element (both, const and non-const). * * The ``MyStack`` implementation is rather simple. It provides a pair of ``pop`` and ``push`` functions and exposes the top element; * as const or non-const reference. * ``pop`` and both ``top`` overloads test whether the inner container is empty and throw conditionally. * \snippet Mock.cpp stack adapter * * To make the test simpler, let's fixate ``T`` as ``int``. * A conforming mock then must provide 5 member functions: * * ``bool empty() const``, * * ``void push_back(int)``, * * ``void pop_back()``, * * ``int& back()`` and * * ``const int& back() const``. * \snippet Mock.cpp container mock * As you can see, mimicpp::Mock accepts any valid signature and even supports overloading (as shown by ``back``). * * Eventually we are able to formulate our tests. The test for ``push`` is rather straight-forward. * \snippet Mock.cpp test push * We create our container mock, setting up the expectation and moving it into the actual stack. * The move is required, because ``MyStack`` doesn't expose the inner container. There are more advanced solutions for that kind of design, * but that would be out of scope of this example. * * The ``pop()`` test is also quite simple, but we should definitely test, that ``pop`` never tries to remove an element from an empty container. * \snippet Mock.cpp test pop * As you can see, the success test sets up two distinct expectations; They may be satisfied in any order, even if only one order here semantically * makes sense. We could use a ``sequence`` object here instead, but with the second test (the empty case) we already have enough confidence. * * The empty test then just creates a single expectation, but in fact it also implicitly tests, that the ``pop_back`` of the container is never called. * * Finally, we should test both of the ``top()`` functions. * \snippet Mock.cpp test top * In the first section we check both overloads, when no elements are present. * * The second section then tests when the container is not empty. * * \{ */ /** * \brief A Mock type, which fully supports overload sets. * \tparam FirstSignature The first signature. * \tparam OtherSignatures Other signatures. */ template <typename FirstSignature, typename... OtherSignatures> requires is_overload_set_v<FirstSignature, OtherSignatures...> class Mock : public detail::MockFrontend<FirstSignature>, public detail::MockFrontend<OtherSignatures>... { public: using detail::MockFrontend<FirstSignature>::operator(); using detail::MockFrontend<FirstSignature>::expect_call; using detail::MockFrontend<OtherSignatures>::operator()...; using detail::MockFrontend<OtherSignatures>::expect_call...; /** * \brief Defaulted destructor. */ ~Mock() = default; /** * \brief Defaulted default constructor. */ [[nodiscard]] Mock() = default; /** * \brief Deleted copy constructor. */ Mock(const Mock&) = delete; /** * \brief Deleted copy assignment operator. */ Mock& operator =(const Mock&) = delete; /** * \brief Defaulted move constructor. */ [[nodiscard]] Mock(Mock&&) = default; /** * \brief Defaulted move assignment operator. */ Mock& operator =(Mock&&) = default; }; /** * \} */ } #endif /*** End of inlined file: Mock.hpp ***/ #endif
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