Thanks for using Compiler Explorer
Sponsors
Jakt
C++
Ada
Analysis
Android Java
Android Kotlin
Assembly
C
C3
Carbon
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
Nim
Objective-C
Objective-C++
OCaml
OpenCL C
Pascal
Pony
Python
Racket
Ruby
Rust
Snowball
Scala
Solidity
Spice
Swift
LLVM TableGen
Toit
TypeScript Native
V
Vala
Visual Basic
WASM
Zig
Javascript
GIMPLE
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 catch_amalgamated.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 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 14.1.0
ARM GCC 14.1.0 (unknown-eabi)
ARM GCC 14.2.0
ARM GCC 14.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 (WINE)
ARM msvc v19.10 (WINE)
ARM msvc v19.14 (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 13.1.0
ARM64 gcc 13.2.0
ARM64 gcc 13.3.0
ARM64 gcc 14.1.0
ARM64 gcc 14.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 (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 13.1.0
AVR gcc 13.2.0
AVR gcc 13.3.0
AVR gcc 14.1.0
AVR gcc 14.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 gcc 13.1.0
BPF gcc 13.2.0
BPF gcc 13.3.0
BPF gcc trunk
EDG (experimental reflection)
EDG 6.5
EDG 6.5 (GNU mode gcc 13)
EDG 6.6
EDG 6.6 (GNU mode gcc 13)
FRC 2019
FRC 2020
FRC 2023
HPPA gcc 14.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)
LoongArch64 clang (trunk)
LoongArch64 clang 17.0.1
LoongArch64 clang 18.1.0
LoongArch64 clang 19.1.0
M68K gcc 13.1.0
M68K gcc 13.2.0
M68K gcc 13.3.0
M68K gcc 14.1.0
M68K gcc 14.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 13.1.0
RISC-V (32-bits) gcc 13.2.0
RISC-V (32-bits) gcc 13.3.0
RISC-V (32-bits) gcc 14.1.0
RISC-V (32-bits) gcc 14.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 13.1.0
RISC-V (64-bits) gcc 13.2.0
RISC-V (64-bits) gcc 13.3.0
RISC-V (64-bits) gcc 14.1.0
RISC-V (64-bits) gcc 14.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 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 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 13.1.0
SPARC LEON gcc 13.2.0
SPARC LEON gcc 13.3.0
SPARC LEON gcc 14.1.0
SPARC LEON gcc 14.2.0
SPARC gcc 12.2.0
SPARC gcc 12.3.0
SPARC gcc 12.4.0
SPARC gcc 13.1.0
SPARC gcc 13.2.0
SPARC gcc 13.3.0
SPARC gcc 14.1.0
SPARC gcc 14.2.0
SPARC64 gcc 12.2.0
SPARC64 gcc 12.3.0
SPARC64 gcc 12.4.0
SPARC64 gcc 13.1.0
SPARC64 gcc 13.2.0
SPARC64 gcc 13.3.0
SPARC64 gcc 14.1.0
SPARC64 gcc 14.2.0
TI C6x gcc 12.2.0
TI C6x gcc 12.3.0
TI C6x gcc 12.4.0
TI C6x gcc 13.1.0
TI C6x gcc 13.2.0
TI C6x gcc 13.3.0
TI C6x gcc 14.1.0
TI C6x gcc 14.2.0
TI CL430 21.6.1
VAX gcc NetBSDELF 10.4.0
VAX gcc NetBSDELF 10.5.0 (Nov 15 03:50:22 2023)
WebAssembly clang (trunk)
Xtensa ESP32 gcc 11.2.0 (2022r1)
Xtensa ESP32 gcc 12.2.0 (20230208)
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 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 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.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 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 9.0.0
armv8-a clang 9.0.1
clang-cl 18.1.0
ellcc 0.1.33
ellcc 0.1.34
ellcc 2017-07-16
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 13.1.0
loongarch64 gcc 13.2.0
loongarch64 gcc 13.3.0
loongarch64 gcc 14.1.0
loongarch64 gcc 14.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 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 13.1.0
mips gcc 13.2.0
mips gcc 13.3.0
mips gcc 14.1.0
mips gcc 14.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 13.1.0
mips64 (el) gcc 13.2.0
mips64 (el) gcc 13.3.0
mips64 (el) gcc 14.1.0
mips64 (el) gcc 14.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 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 13.1.0
mips64 gcc 13.2.0
mips64 gcc 13.3.0
mips64 gcc 14.1.0
mips64 gcc 14.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
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 gcc 12.1.0
mipsel gcc 12.2.0
mipsel gcc 12.3.0
mipsel gcc 12.4.0
mipsel gcc 13.1.0
mipsel gcc 13.2.0
mipsel gcc 13.3.0
mipsel gcc 14.1.0
mipsel gcc 14.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 13.1.0
power gcc 13.2.0
power gcc 13.3.0
power gcc 14.1.0
power gcc 14.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 13.1.0
power64 gcc 13.2.0
power64 gcc 13.3.0
power64 gcc 14.1.0
power64 gcc 14.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 13.1.0
power64le gcc 13.2.0
power64le gcc 13.3.0
power64le gcc 14.1.0
power64le gcc 14.2.0
power64le gcc 6.3.0
power64le gcc trunk
powerpc64 clang (trunk)
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 13.1.0
s390x gcc 13.2.0
s390x gcc 13.3.0
s390x gcc 14.1.0
s390x gcc 14.2.0
sh gcc 12.2.0
sh gcc 12.3.0
sh gcc 12.4.0
sh gcc 13.1.0
sh gcc 13.2.0
sh gcc 13.3.0
sh gcc 14.1.0
sh gcc 14.2.0
sh gcc 4.9.4
sh gcc 9.5.0
vast (trunk)
x64 msvc v19.0 (WINE)
x64 msvc v19.10 (WINE)
x64 msvc v19.14 (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.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 (WINE)
x86 msvc v19.10 (WINE)
x86 msvc v19.14 (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.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.3
x86 nvc++ 24.5
x86 nvc++ 24.7
x86-64 Zapcc 190308
x86-64 clang (EricWF contracts)
x86-64 clang (amd-staging)
x86-64 clang (assertions trunk)
x86-64 clang (clangir)
x86-64 clang (dascandy contracts)
x86-64 clang (experimental -Wlifetime)
x86-64 clang (experimental P1061)
x86-64 clang (experimental P1144)
x86-64 clang (experimental P1221)
x86-64 clang (experimental P2996)
x86-64 clang (experimental P3068)
x86-64 clang (experimental P3309)
x86-64 clang (experimental P3367)
x86-64 clang (experimental P3372)
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)
x86-64 clang (resugar)
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 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 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.4
x86-64 gcc 10.5
x86-64 gcc 11.1
x86-64 gcc 11.2
x86-64 gcc 11.3
x86-64 gcc 11.4
x86-64 gcc 12.1
x86-64 gcc 12.2
x86-64 gcc 12.3
x86-64 gcc 12.4
x86-64 gcc 13.1
x86-64 gcc 13.2
x86-64 gcc 13.3
x86-64 gcc 14.1
x86-64 gcc 14.2
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 (latest)
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
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.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> struct signature_add_noexcept; template <typename Signature> using signature_add_noexcept_t = typename signature_add_noexcept<Signature>::type; template <typename Signature> struct signature_remove_noexcept; template <typename Signature> using signature_remove_noexcept_t = typename signature_remove_noexcept<Signature>::type; template <typename Signature> struct signature_decay; template <typename Signature> using signature_decay_t = typename signature_decay<Signature>::type; template <typename Signature> struct signature_return_type; template <typename Signature> using signature_return_type_t = typename signature_return_type<Signature>::type; template <std::size_t index, typename Signature> struct signature_param_type; template <std::size_t index, typename Signature> using signature_param_type_t = typename signature_param_type<index, Signature>::type; template <typename Signature> struct signature_param_list; template <typename Signature> using signature_param_list_t = typename signature_param_list<Signature>::type; template <typename First, typename Second> struct is_overloadable_with; 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, typename... Others> inline constexpr bool is_overload_set_v = is_overload_set<First, Others...>::value; template <typename T> struct is_character; template <typename T> struct string_traits; template <typename Signature> requires std::same_as<Signature, signature_decay_t<Signature>> class Expectation; template <typename Signature> requires std::same_as<Signature, signature_decay_t<Signature>> class ExpectationCollection; class ScopedExpectation; enum class MatchResult { none, inapplicable, full }; enum class Constness { non_const = 0b01, as_const = 0b10, any = non_const | as_const }; enum class ValueCategory { lvalue = 0b01, rvalue = 0b10, any = lvalue | rvalue }; class CallReport; class MatchReport; class ExpectationReport; using CharT = char; using CharTraitsT = std::char_traits<CharT>; using StringT = std::basic_string<CharT, CharTraitsT>; } namespace mimicpp::sequence { enum Tag : std::ptrdiff_t { }; enum class Id : int { }; struct rating { int priority{}; Tag tag{}; [[nodiscard]] friend bool operator==(const rating&, const rating&) = default; }; } #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 <source_location> #include <string_view> #include <tuple> #include <utility> namespace mimicpp { template <typename...> struct always_false : public std::bool_constant<false> { }; template <std::size_t priority> struct priority_tag /** \cond Help doxygen with recursion.*/ : public priority_tag<priority - 1> /** \endcond */ { }; template <> struct priority_tag<0> { }; [[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, typename... Others> concept same_as_any = (... || std::same_as<T, Others>); 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); } template <typename T, template <typename> typename Trait> concept satisfies = Trait<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 [[nodiscard]] constexpr bool is_matching(const Constness lhs, const Constness rhs) noexcept { return std::cmp_not_equal(0, to_underlying(lhs) & to_underlying(rhs)); } [[nodiscard]] constexpr bool is_matching(const ValueCategory lhs, const ValueCategory rhs) noexcept { return std::cmp_not_equal(0, to_underlying(lhs) & to_underlying(rhs)); } } namespace mimicpp::detail { template <typename Parsed, typename... Rest> struct unique; template <typename... Uniques, typename First, typename... Others> struct unique< std::tuple<Uniques...>, First, Others...> { using current_t = std::conditional_t< same_as_any<First, Uniques...>, std::tuple<Uniques...>, std::tuple<Uniques..., First>>; using type_t = typename unique< current_t, Others...>::type_t; }; template <typename... Uniques> struct unique<std::tuple<Uniques...>> { using type_t = std::tuple<Uniques...>; }; template <typename... Types> using unique_list_t = typename unique<std::tuple<>, Types...>::type_t; } #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 <concepts> #include <cstddef> #include <tuple> #include <type_traits> namespace mimicpp { 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 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 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> requires std::is_function_v<Signature> struct signature_return_type<Signature> : public signature_return_type<signature_decay_t<Signature>> { }; 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> 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 Return, typename... Params> struct signature_param_type<index, Return(Params...)> : public std::tuple_element<index, std::tuple<Params...>> { }; template <typename Signature> requires std::is_function_v<Signature> struct signature_param_list<Signature> : public signature_param_list< signature_decay_t<Signature>> { }; 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> 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...>> { }; } #endif /*** End of inlined file: TypeTraits.hpp ***/ /*** Start of inlined file: String.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_STRING_HPP #define MIMICPP_STRING_HPP #pragma once #include <bit> #include <concepts> #include <functional> #include <ranges> #include <string> #include <string_view> #include <type_traits> namespace mimicpp { /** * \defgroup STRING string * \brief Contains symbols for generic string processing. */ /** * \defgroup TYPE_TRAITS_IS_CHARACTER is_character * \ingroup STRING * \ingroup TYPE_TRAITS * \brief Type-trait, which determines, whether the given type is a character-type. * \attention User are not allowed to add specializations to the trait by themselves. * * \{ */ /** * \brief Primary template, which always yields ``false``. * \tparam T Type to check. */ template <typename T> struct is_character : public std::false_type { }; /** * \brief Convenience boolean-constant to the result of ``is_character`` trait. * \tparam T The type to check. */ template <typename T> inline constexpr bool is_character_v{is_character<T>::value}; /** * \brief Specialization for ``char``. */ template <> struct is_character<char> : public std::true_type { }; /** * \brief Specialization for ``signed char``. */ template <> struct is_character<signed char> : public std::true_type { }; /** * \brief Specialization for ``unsigned char``. */ template <> struct is_character<unsigned char> : public std::true_type { }; /** * \brief Specialization for ``wchar_t``. */ template <> struct is_character<wchar_t> : public std::true_type { }; /** * \brief Specialization for ``char8_t``. */ template <> struct is_character<char8_t> : public std::true_type { }; /** * \brief Specialization for ``char16_t``. */ template <> struct is_character<char16_t> : public std::true_type { }; /** * \brief Specialization for ``char32_t``. */ template <> struct is_character<char32_t> : public std::true_type { }; /** * \} */ /** * \defgroup TYPE_TRAITS_STRING_TRAITS string_traits * \ingroup STRING * \ingroup TYPE_TRAITS * \brief Type-trait, which contains properties for the provided string type. * * \{ */ /** * \brief Computes the view type for the given string. * \tparam T Type to check. */ template <typename T> using string_view_t = decltype(string_traits<std::remove_cvref_t<T>>::view(std::declval<T&>())); /** * \brief Computes the character type for the given string. * \tparam T Type to check. */ template <typename T> using string_char_t = typename string_traits<std::remove_cvref_t<T>>::char_t; /** * \brief Specialization for character pointer types. * \tparam T Type to check. */ template <typename T> requires std::is_pointer_v<T> && is_character_v<std::remove_cv_t<std::remove_pointer_t<T>>> struct string_traits<T> { using char_t = std::remove_const_t<std::remove_pointer_t<T>>; using view_t = std::basic_string_view<char_t>; [[nodiscard]] static constexpr view_t view(const std::remove_pointer_t<T>* str) noexcept { return view_t{str}; } }; /** * \brief Specialization for character array types. * \tparam T Type to check. */ template <typename T> requires std::is_array_v<T> struct string_traits<T> : public string_traits<std::remove_extent_t<T>*> { }; /** * \brief Specialization for ``std::basic_string`` types. * \tparam T Type to check. */ template <typename Char, typename Traits, typename Allocator> struct string_traits<std::basic_string<Char, Traits, Allocator>> { using char_t = Char; using string_t = std::basic_string<Char, Traits, Allocator>; using view_t = std::basic_string_view<Char, Traits>; [[nodiscard]] static constexpr view_t view(const string_t& str) noexcept { return view_t{str}; } }; /** * \brief Specialization for ``std::basic_string_view`` types. * \tparam T Type to check. */ template <typename Char, typename Traits> struct string_traits<std::basic_string_view<Char, Traits>> { using char_t = Char; using view_t = std::basic_string_view<Char, Traits>; [[nodiscard]] static constexpr view_t view(view_t str) noexcept { return str; } }; /** * \} */ /** * \brief Determines, whether the given type can be used as a string-type. * \ingroup STRING * \ingroup CONCEPTS */ template <typename T> concept string = requires { requires is_character_v<string_char_t<T>>; requires std::ranges::contiguous_range<string_view_t<T>>; requires std::ranges::sized_range<string_view_t<T>>; requires std::ranges::borrowed_range<string_view_t<T>>; requires std::same_as< string_char_t<T>, std::ranges::range_value_t<string_view_t<T>>>; }; /** * \defgroup TYPE_TRAITS_STRING_CASE_FOLD_CONVERTER string_case_fold_converter * \ingroup STRING * \brief Type-trait, which contains properties for the provided string type. * \details This implements the case-folding algorithm for the various character-types. * Users should not make any assumptions on what any converter will return, as this is highly * implementation specific. No guarantees are made whether the result will be in upper- or lower-case (or something completely * different). The only guarantee is, that two inputs, which are required to compare equal after the case-folding process, * yield the same result. * * \see https://unicode-org.github.io/icu/userguide/transforms/casemappings.html#case-folding * \see https://www.unicode.org/Public/UNIDATA/CaseFolding.txt */ /** * \brief Primary template, purposely undefined. * \tparam Char The character type. * \ingroup TYPE_TRAITS_STRING_CASE_FOLD_CONVERTER */ template <satisfies<is_character> Char> struct string_case_fold_converter; /** * \brief Determines, whether the given type supports string normalization. * \ingroup STRING * \ingroup CONCEPTS */ template <typename String> concept case_foldable_string = string<String> && requires(const string_case_fold_converter<string_char_t<String>> converter, string_view_t<String> view) { { std::invoke(converter, std::move(view)) } -> std::ranges::forward_range; } && requires(std::invoke_result_t<string_case_fold_converter<string_char_t<String>>, string_view_t<String>> normalized) { requires std::same_as< string_char_t<String>, std::ranges::range_value_t<decltype(normalized)>>; }; } namespace mimicpp::detail { template <typename View, typename Char> concept compatible_string_view_with = is_character_v<Char> && std::ranges::borrowed_range<View> && std::ranges::contiguous_range<View> && std::ranges::sized_range<View> && std::same_as<Char, std::ranges::range_value_t<View>>; } #ifndef MIMICPP_CONFIG_EXPERIMENTAL_UNICODE_STR_MATCHER /** * \brief Specialized template for the ``char`` type. * \ingroup TYPE_TRAITS_STRING_CASE_FOLD_CONVERTER */ template <> struct mimicpp::string_case_fold_converter<char> { template <detail::compatible_string_view_with<char> String> [[nodiscard]] constexpr auto operator ()(String&& str) const { return std::views::all(std::forward<String>(str)) | std::views::transform( [](const char c) noexcept { // see notes of https://en.cppreference.com/w/cpp/string/byte/toupper // This approach will fail, str actually contains an utf8-encoded string. return static_cast<char>( static_cast<unsigned char>(std::toupper(c))); }); } }; #else // is missing from the unicodelib headers #include <cstdint> #include <unicodelib.h> #include <unicodelib_encodings.h> /** * \brief Specialized template for the ``char`` type (with unicodelib backend). * \ingroup TYPE_TRAITS_STRING_CASE_FOLD_CONVERTER */ template <> struct mimicpp::string_case_fold_converter<char> { template <detail::compatible_string_view_with<char> String> [[nodiscard]] constexpr auto operator ()(String&& str) const { return unicode::utf8::encode( unicode::to_case_fold( unicode::utf8::decode( std::string_view{ std::ranges::data(str), std::ranges::size(str) }))); } }; /** * \brief Specialized template for the ``wchar_t`` type (with unicodelib backend). * \ingroup TYPE_TRAITS_STRING_CASE_FOLD_CONVERTER */ template <> struct mimicpp::string_case_fold_converter<wchar_t> { template <detail::compatible_string_view_with<wchar_t> String> [[nodiscard]] constexpr auto operator ()(String&& str) const { return unicode::to_wstring( unicode::to_case_fold( unicode::to_utf32( std::wstring_view{ std::ranges::data(str), std::ranges::size(str) }))); } }; /** * \brief Specialized template for the ``char8_t`` type (with unicodelib backend). * \ingroup TYPE_TRAITS_STRING_CASE_FOLD_CONVERTER */ template <> struct mimicpp::string_case_fold_converter<char8_t> { template <detail::compatible_string_view_with<char8_t> String> [[nodiscard]] constexpr auto operator ()(String&& str) const { const std::string caseFolded = std::invoke( mimicpp::string_case_fold_converter<char>{}, std::string_view{ std::bit_cast<const char*>(std::ranges::data(str)), std::ranges::size(str) }); return std::u8string{ caseFolded.cbegin(), caseFolded.cend() }; } }; /** * \brief Specialized template for the ``char16_t`` type (with unicodelib backend). * \ingroup TYPE_TRAITS_STRING_CASE_FOLD_CONVERTER */ template <> struct mimicpp::string_case_fold_converter<char16_t> { template <detail::compatible_string_view_with<char16_t> String> [[nodiscard]] constexpr auto operator ()(String&& str) const { return unicode::utf16::encode( unicode::to_case_fold( unicode::utf16::decode( std::u16string_view{ std::ranges::data(str), std::ranges::size(str) }))); } }; /** * \brief Specialized template for the ``char32_t`` type (with unicodelib backend). * \ingroup TYPE_TRAITS_STRING_CASE_FOLD_CONVERTER */ template <> struct mimicpp::string_case_fold_converter<char32_t> { template <detail::compatible_string_view_with<char32_t> String> [[nodiscard]] constexpr auto operator ()(String&& str) const { return unicode::to_case_fold( std::u32string_view{ std::ranges::data(str), std::ranges::size(str) }); } }; #endif #endif /*** End of inlined file: String.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 <cstdint> #include <functional> #include <iterator> #include <ranges> #include <source_location> #include <sstream> #include <string> #include <string_view> #include <utility> #ifndef MIMICPP_CONFIG_USE_FMT #include <format> #else #if __has_include(<fmt/format.h>) #include <fmt/format.h> #else #error "The fmt formatting backend is explicitly enabled, but the include <fmt/format.h> can not be found." #endif #endif 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, const CharT&>; template <typename Printer, typename OutIter, typename T> concept printer_for = print_iterator<OutIter> && requires(OutIter out) { { Printer::print(out, std::declval<T&&>()) } -> std::convertible_to<OutIter>; }; } namespace mimicpp::format { #ifndef MIMICPP_CONFIG_USE_FMT // use std format #ifndef _LIBCPP_VERSION using std::formatter; 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::formatter; 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( std::move(out), fmt, std::make_format_args(args...)); } #endif namespace detail { 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>; }; } // use fmt format #else using fmt::formatter; using fmt::format; using fmt::format_to; using fmt::vformat; using fmt::vformat_to; using fmt::make_format_args; namespace detail { template <class T, class Char> concept formattable = fmt::is_formattable<std::remove_reference_t<T>, Char>::value; } #endif } template <> struct mimicpp::format::formatter<mimicpp::ValueCategory, mimicpp::CharT> : public 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 formatter<std::string_view, mimicpp::CharT>::format( toString(category), ctx); } }; template <> struct mimicpp::format::formatter<mimicpp::Constness, mimicpp::CharT> : public 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 formatter<std::string_view, mimicpp::CharT>::format( toString(category), ctx); } }; namespace mimicpp::custom { /** * \brief User may add specializations, which will then be used during ``print`` calls. * \ingroup STRINGIFICATION */ template <typename> class Printer; } namespace mimicpp::detail { template < print_iterator OutIter, typename T, printer_for<OutIter, T> Printer = custom::Printer<std::remove_cvref_t<T>>> OutIter print( OutIter out, T&& value, [[maybe_unused]] const priority_tag<5> ) { return Printer::print( std::move(out), std::forward<T>(value)); } template <typename> class Printer; template < print_iterator OutIter, typename T, printer_for<OutIter, T> Printer = Printer<std::remove_cvref_t<T>>> OutIter print( OutIter out, T&& value, [[maybe_unused]] const priority_tag<4> ) { return Printer::print( std::move(out), std::forward<T>(value)); } template <print_iterator OutIter, std::convertible_to<StringViewT> String> OutIter print( OutIter out, String&& str, [[maybe_unused]] const priority_tag<3> ) { return format::format_to( std::move(out), "\"{}\"", static_cast<StringViewT>(std::forward<String>(str))); } template <print_iterator OutIter, std::ranges::forward_range Range> OutIter print( OutIter out, Range&& range, priority_tag<2> ); template <print_iterator OutIter, format::detail::formattable<CharT> T> OutIter print( OutIter out, T&& value, [[maybe_unused]] const priority_tag<1> ) { return format::format_to( std::move(out), "{}", std::forward<T>(value)); } template <print_iterator OutIter> OutIter print( OutIter out, auto&&, [[maybe_unused]] const priority_tag<0> ) { return format::format_to( std::move(out), "{{?}}"); } class PrintFn { public: template <print_iterator OutIter, typename T> OutIter operator ()( OutIter out, T&& value ) const { static_assert( requires(const priority_tag<6> tag) { { print(out, std::forward<T>(value), tag) } -> std::convertible_to<OutIter>; }, "The given type is not printable. "); return print( std::move(out), std::forward<T>(value), priority_tag<6>{}); } template <typename T> StringT operator ()(T&& value) const { StringStreamT stream{}; std::invoke( *this, std::ostreambuf_iterator{stream}, std::forward<T>(value)); return std::move(stream).str(); } }; template <print_iterator OutIter, std::ranges::forward_range Range> OutIter print( OutIter out, Range&& range, // NOLINT(cppcoreguidelines-missing-std-forward) const priority_tag<2> ) { out = format::format_to( std::move(out), "{{ "); auto iter = std::ranges::begin(range); if (const auto end = std::ranges::end(range); iter != end) { constexpr PrintFn print{}; out = print(std::move(out), *iter++); for (; iter != end; ++iter) { out = print( format::format_to(std::move(out), ", "), *iter); } } return format::format_to( std::move(out), " }}"); } template <> class Printer<std::source_location> { public: template <print_iterator OutIter> static OutIter print(OutIter out, const std::source_location& loc) { return format::format_to( std::move(out), "{}[{}:{}], {}", loc.file_name(), loc.line(), loc.column(), loc.function_name()); } }; template <typename Char> requires is_character_v<Char> struct character_literal_printer; template <> struct character_literal_printer<char> { template <print_iterator OutIter> static OutIter print(OutIter out) noexcept { // no special character-literal return out; } }; template <> struct character_literal_printer<wchar_t> { template <print_iterator OutIter> static OutIter print(OutIter out) { return format::format_to(std::move(out), "L"); } }; template <> struct character_literal_printer<char8_t> { template <print_iterator OutIter> static OutIter print(OutIter out) { return format::format_to(std::move(out), "u8"); } }; template <> struct character_literal_printer<char16_t> { template <print_iterator OutIter> static OutIter print(OutIter out) { return format::format_to(std::move(out), "u"); } }; template <> struct character_literal_printer<char32_t> { template <print_iterator OutIter> static OutIter print(OutIter out) { return format::format_to(std::move(out), "U"); } }; template <string String> requires (!std::same_as<CharT, string_char_t<String>>) class Printer<String> { public: template <std::common_reference_with<String> T, print_iterator OutIter> static OutIter print(OutIter out, T&& str) { using intermediate_t = std::uint32_t; static_assert(sizeof(string_char_t<String>) <= sizeof(intermediate_t)); out = character_literal_printer<string_char_t<String>>::print(std::move(out)); out = format::format_to(std::move(out), "\""); auto view = string_traits<std::remove_cvref_t<T>>::view(std::forward<T>(str)); auto iter = std::ranges::begin(view); if (const auto end = std::ranges::end(view); iter != end) { out = format::format_to( std::move(out), "{:#x}", static_cast<intermediate_t>(*iter++)); for (; iter != end; ++iter) { out = format::format_to( std::move(out), ", {:#x}", static_cast<intermediate_t>(*iter)); } } return format::format_to(std::move(out), "\""); } }; } namespace mimicpp { /** * \defgroup STRINGIFICATION stringification * \brief Stringification describes the process of converting an object state into its textual representation. * \details ``mimic++`` often wants to present users a textual representation of their tested objects, * because they might have failed a test case. It utilizes the ``print`` function for that purpose. * * That function internally checks for the first available option (in that order): * - ``mimicpp::custom::Printer`` specialization * - internal printer specializations * - convertible to ``std::string_view`` * - satisfies ``std::ranges::forward_range`` * - formattable type (in terms of the installed format-backend) * * If no valid alternative has been found, the default is chosen (which just prints "{?}"). * * ## Override existing printings or print custom types * * As ``mimic++`` can not know how to convert any custom type out there, a simple but effective mechanism is used. * Users can add a specialization of ``mimicpp::custom::Printer`` for their own or third-party types. * ``mimic++`` will always prefer such a specialization over any other internal alternative, even if ``mimic++`` already * has special treatment that particular type (e.g. ``std::source_location``). * * Given the following type. * \snippet CustomPrinter.cpp my_type * Users can then create a specialization as follows: * \snippet CustomPrinter.cpp my_type printer * * When an object of ``my_type`` is then passed to ``print``, that specification will be used: * \snippet CustomPrinter.cpp my_type print * * ## String printing * * All char-strings (like ``const char*`` or ``std::string``) will be printed as they are. * Other character-types (e.g. ``wchar_t`` or ``char8_t``) must be treated with care. Making ``mimic++`` 100% compatible with any * existing character-type is either a major work-load or has to be outsourced to a dependency. * Currently, ``mimic++`` chooses another option: If a string of a non-printable character-type (in terms of the type-trait ``is_character``) * is detected, it prints the string-literal and all elements are converted to their value-representation and printed as comma separated hex-values. * * For example, the string ``u8"Hello, World!"`` will then be printed as * ``u8"0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21"``. * *\{ */ /** * \brief Functional object, converting the given object to its textual representation. */ inline constexpr detail::PrintFn print{}; /** * \} */ } #endif /*** End of inlined file: Printer.hpp ***/ /*** Start of inlined file: Reports.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_REPORTS_HPP #define MIMICPP_REPORTS_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 /** \cond Help doxygen with recursion.*/ : public info_for_signature<signature_decay_t<Signature>> /** \endcond */ { }; 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 <functional> #include <iterator> #include <optional> #include <ranges> #include <typeindex> #include <variant> #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. * * Each report type can be printed via ``mimicpp::print`` function, but users may customize that by adding a specialization for * ``mimicpp::custom::Printer``. * \{ */ struct state_inapplicable { int min{}; int max{}; int count{}; std::vector<sequence::rating> sequenceRatings{}; std::vector<sequence::Tag> inapplicableSequences{}; [[nodiscard]] friend bool operator ==(const state_inapplicable&, const state_inapplicable&) = default; }; struct state_applicable { int min{}; int max{}; int count{}; std::vector<sequence::rating> sequenceRatings{}; [[nodiscard]] friend bool operator ==(const state_applicable&, const state_applicable&) = default; }; struct state_saturated { int min{}; int max{}; int count{}; std::vector<sequence::Tag> sequences{}; [[nodiscard]] friend bool operator ==(const state_saturated&, const state_saturated&) = default; }; using control_state_t = std::variant< state_inapplicable, state_applicable, state_saturated>; namespace detail { template <print_iterator OutIter> OutIter print_times_state(OutIter out, const std::size_t current, const std::size_t min, const std::size_t max) { const auto verbalizeValue = [](OutIter o, const std::size_t value) { switch (value) { case 0: return format::format_to(std::move(o), "never"); case 1: return format::format_to(std::move(o), "once"); case 2: return format::format_to(std::move(o), "twice"); default: return format::format_to(std::move(o), "{} times", value); } }; if (current == max) { out = format::format_to( std::move(out), "already saturated (matched "); out = verbalizeValue(std::move(out), current); return format::format_to(std::move(out),")"); } if (min <= current) { return format::format_to( std::move(out), "accepts further matches (matched {} out of {} times)", current, max); } const auto verbalizeInterval = [verbalizeValue](OutIter o, const std::size_t start, const std::size_t end) { if (start < end) { return format::format_to( std::move(o), "between {} and {} times", start, end); } o = format::format_to(std::move(o), "exactly "); return verbalizeValue(std::move(o), end); }; out = format::format_to(std::move(out), "matched "); out = verbalizeValue(std::move(out), current); out = format::format_to(std::move(out), " - "); out = verbalizeInterval(std::move(out), min, max); return format::format_to(std::move(out), " is expected"); } struct control_state_printer { template <print_iterator OutIter> OutIter operator ()(OutIter out, const state_applicable& state) const { out = print_times_state( std::move(out), state.count, state.min, state.max); if (const auto sequenceCount = std::ranges::ssize(state.sequenceRatings); 0 < sequenceCount) { out = format::format_to( std::move(out), ",\n\tand is the current element of {} sequence(s).", sequenceCount); } return out; } template <print_iterator OutIter> OutIter operator ()(OutIter out, const state_inapplicable& state) const { out = print_times_state( std::move(out), state.count, state.min, state.max); const auto totalSequences = std::ranges::ssize(state.sequenceRatings) + std::ranges::ssize(state.inapplicableSequences); return format::format_to( std::move(out), ",\n\tbut is not the current element of {} sequence(s) ({} total).", std::ranges::ssize(state.inapplicableSequences), totalSequences); } template <print_iterator OutIter> OutIter operator ()(OutIter out, const state_saturated& state) const { out = print_times_state( std::move(out), state.count, state.min, state.max); if (const auto sequenceCount = std::ranges::ssize(state.sequences); 0 < sequenceCount) { out = format::format_to( std::move(out), ",\n\tand is part of {} sequence(s).", sequenceCount); } return out; } }; } /** * \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 }; } template <> class detail::Printer<CallReport> { public: template <print_iterator OutIter> static OutIter print(OutIter out, const CallReport& report) { out = format::format_to( std::move(out), "call from "); out = mimicpp::print( std::move(out), report.fromLoc); out = format::format_to( std::move(out), "\n"); out = format::format_to( std::move(out), "constness: {}\n" "value category: {}\n" "return type: {}\n", report.fromConstness, report.fromCategory, report.returnTypeIndex.name()); if (!std::ranges::empty(report.argDetails)) { out = format::format_to( std::move(out), "args:\n"); for (const std::size_t i : std::views::iota(0u, std::ranges::size(report.argDetails))) { out = format::format_to( std::move(out), "\targ[{}]: {{\n" "\t\ttype: {},\n" "\t\tvalue: {}\n" "\t}},\n", i, report.argDetails[i].typeIndex.name(), report.argDetails[i].stateString); } } return out; } }; /** * \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)); } }; template <> class detail::Printer<ExpectationReport> { public: template <print_iterator OutIter> static OutIter print(OutIter out, const ExpectationReport& report) { out = format::format_to( std::move(out), "Expectation report:\n"); if (report.sourceLocation) { out = format::format_to( std::move(out), "from: "); out = mimicpp::print( std::move(out), *report.sourceLocation); out = format::format_to( std::move(out), "\n"); } if (report.timesDescription) { out = format::format_to( out, "times: {}\n", *report.timesDescription); } if (std::ranges::any_of( report.expectationDescriptions, [](const auto& desc) { return desc.has_value(); })) { out = format::format_to( std::move(out), "expects:\n"); for (const auto& desc : report.expectationDescriptions | std::views::filter([](const auto& desc) { return desc.has_value(); })) { out = format::format_to( std::move(out), "\t{},\n", *desc); } } if (report.finalizerDescription) { out = format::format_to( std::move(out), "finally: {}\n", *report.finalizerDescription); } return out; } }; /** * \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 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{}; control_state_t controlReport{}; std::vector<Expectation> expectationReports{}; [[nodiscard]] friend bool operator ==(const MatchReport& lhs, const MatchReport& rhs) { return lhs.finalizeReport == rhs.finalizeReport && lhs.controlReport == rhs.controlReport && 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 (!std::holds_alternative<state_applicable>(report.controlReport)) { return MatchResult::inapplicable; } return MatchResult::full; } template <> class detail::Printer<MatchReport> { public: template <print_iterator OutIter> static OutIter print(OutIter out, 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); } } } switch (evaluate_match_report(report)) { case MatchResult::full: out = format::format_to( std::move(out), "Matched expectation: {{\n"); break; case MatchResult::inapplicable: out = format::format_to( std::move(out), "Inapplicable, but otherwise matched expectation: {{\n" "reason: "); out = std::visit( std::bind_front(control_state_printer{}, std::move(out)), report.controlReport); out = format::format_to(std::move(out), "\n"); break; case MatchResult::none: out = format::format_to( std::move(out), "Unmatched expectation: {{\n"); break; // GCOVR_EXCL_START default: // NOLINT(clang-diagnostic-covered-switch-default) unreachable(); // GCOVR_EXCL_STOP } if (report.sourceLocation) { out = format::format_to( std::move(out), "from: "); out = mimicpp::print( std::move(out), *report.sourceLocation); out = format::format_to( std::move(out), "\n"); } if (!std::ranges::empty(unmatchedExpectationDescriptions)) { out = format::format_to( std::move(out), "failed:\n"); for (const auto& desc : unmatchedExpectationDescriptions) { out = format::format_to( std::move(out), "\t{},\n", desc); } } if (!std::ranges::empty(matchedExpectationDescriptions)) { out = format::format_to( std::move(out), "passed:\n"); for (const auto& desc : matchedExpectationDescriptions) { out = format::format_to( std::move(out), "\t{},\n", desc); } } return format::format_to( std::move(out), "}}\n"); } }; /** * \} */ } #endif /*** End of inlined file: Reports.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 #include <algorithm> #include <cassert> #include <exception> #include <functional> #include <memory> #include <optional> #include <ranges> #include <vector> namespace mimicpp { /** * \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{}; ss << "No match for "; print( std::ostreambuf_iterator{ss}, call); ss << "\n"; if (std::ranges::empty(matchReports)) { ss << "No expectations available.\n"; } else { format::format_to( std::ostreambuf_iterator{ss}, "{} available expectation(s):\n", std::ranges::size(matchReports)); for (const auto& report : matchReports) { print( std::ostreambuf_iterator{ss}, report); ss << "\n"; } } send_fail(std::move(ss).str()); } [[noreturn]] void report_inapplicable_matches(const CallReport call, const std::vector<MatchReport> matchReports) override { StringStreamT ss{}; ss << "No applicable match for "; print( std::ostreambuf_iterator{ss}, call); ss << "\n"; ss << "Tested expectations:\n"; for (const auto& report : matchReports) { print( std::ostreambuf_iterator{ss}, report); ss << "\n"; } send_fail(std::move(ss).str()); } void report_full_match(const CallReport call, const MatchReport matchReport) noexcept override { StringStreamT ss{}; ss << "Found match for "; print( std::ostreambuf_iterator{ss}, call); ss << "\n"; print( std::ostreambuf_iterator{ss}, matchReport); ss << "\n"; send_success(std::move(ss).str()); } void report_unfulfilled_expectation(const ExpectationReport expectationReport) override { if (0 == std::uncaught_exceptions()) { StringStreamT ss{}; ss << "Unfulfilled expectation:\n"; print( std::ostreambuf_iterator{ss}, expectationReport); ss << "\n"; send_fail(std::move(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) { ss << "what: " << e.what() << "\n"; } catch (...) { ss << "Unknown exception type.\n"; } ss << "while checking expectation:\n"; print( std::ostreambuf_iterator{ss}, expectationReport); ss << "\n"; ss << "For "; print( std::ostreambuf_iterator{ss}, call); ss << "\n"; send_warning(std::move(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( [[maybe_unused]] CallReport call, [[maybe_unused]] 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( [[maybe_unused]] CallReport call, [[maybe_unused]] ExpectationReport expectationReport, [[maybe_unused]] 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> #include <span> #include <tuple> namespace mimicpp::sequence { /** * \defgroup EXPECTATION_SEQUENCE sequence * \ingroup EXPECTATION * \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. * * # A word on sequences with times * Sequences and times are fully compatible, but can quickly lead to very hard to understand flows. * In general, when mixing sequences with exact times (like expect::twice) it will just work. But when users are defining very * permissive expectations combined with ranging times (like binary expect::times), that may lead to surprising behaviour. * * Imagine a Sequence with two expectations; the first one with ``min = 0`` and ``max = 1`` and the second one with arbitrary limits. * When a new call matches both expectations, which one should be preferred? * * As a last resort, sequences come in two built-in flavors: * - LazySequence (default) * - GreedySequence * * LazySequence is designed to make the least possible progress for a match, thus prefers not to skip sequence elements: * \snippet Sequences.cpp lazy * * GreedySequence does make the maximal possible progress for a match, thus prefers to skip sequence elements: * \snippet Sequences.cpp greedy */ namespace detail { template <typename Id, auto priorityStrategy> requires std::is_enum_v<Id> && std::signed_integral<std::underlying_type_t<Id>> && std::convertible_to< std::invoke_result_t<decltype(priorityStrategy), Id, int>, int> class BasicSequence { public: using IdT = Id; ~BasicSequence() noexcept(false) { const auto iter = std::ranges::find_if_not( m_Entries.cbegin() + m_Cursor, m_Entries.cend(), [](const State s) noexcept { return s == State::satisfied || s == State::saturated; }); if (iter != m_Entries.cend()) { mimicpp::detail::report_error( format::format( "Unfulfilled sequence. {} out of {} expectation(s) are satisfied.", std::ranges::distance(m_Entries.cbegin(), iter), m_Entries.size())); } } [[nodiscard]] BasicSequence() = default; BasicSequence(const BasicSequence&) = delete; BasicSequence& operator =(const BasicSequence&) = delete; BasicSequence(BasicSequence&&) = delete; BasicSequence& operator =(BasicSequence&&) = delete; [[nodiscard]] constexpr std::optional<int> priority_of(const IdT id) const noexcept { assert(is_valid(id)); if (is_consumable(id)) { return std::invoke( priorityStrategy, id, m_Cursor); } return std::nullopt; } constexpr void set_satisfied(const IdT id) noexcept { assert(is_valid(id)); const auto index = to_underlying(id); assert(m_Cursor <= index); auto& element = m_Entries[to_underlying(id)]; assert(element == State::unsatisfied); element = State::satisfied; } constexpr void set_saturated(const IdT id) noexcept { assert(is_valid(id)); const auto index = to_underlying(id); assert(m_Cursor <= index); auto& element = m_Entries[index]; assert( element == State::unsatisfied || element == State::satisfied); element = State::saturated; } [[nodiscard]] constexpr bool is_consumable(const IdT id) const noexcept { assert(is_valid(id)); const int index = to_underlying(id); const auto state = m_Entries[index]; return m_Cursor <= index && std::ranges::all_of( m_Entries.begin() + m_Cursor, m_Entries.begin() + index, [](const State s) noexcept { return s == State::satisfied || s == State::saturated; }) && (state == State::unsatisfied || state == State::satisfied); } constexpr void consume(const IdT id) noexcept { assert(is_consumable(id)); m_Cursor = to_underlying(id); } [[nodiscard]] constexpr IdT add() { if (!std::in_range<std::underlying_type_t<IdT>>(m_Entries.size())) [[unlikely]] { throw std::runtime_error{ "Sequence already holds maximum amount of elements." }; } m_Entries.emplace_back(State::unsatisfied); return static_cast<IdT>(m_Entries.size() - 1); } [[nodiscard]] constexpr Tag tag() const noexcept { return Tag{ std::bit_cast<std::ptrdiff_t>(this) }; } private: enum class State { unsatisfied, satisfied, saturated }; std::vector<State> m_Entries{}; int m_Cursor{}; [[nodiscard]] constexpr bool is_valid(const IdT id) const noexcept { return 0 <= to_underlying(id) && std::cmp_less(to_underlying(id), m_Entries.size()); } }; class LazyStrategy { public: [[nodiscard]] constexpr int operator ()(const auto id, const int cursor) const noexcept { const auto index = to_underlying(id); assert(std::cmp_less_equal(cursor, index)); return std::numeric_limits<int>::max() - (static_cast<int>(index) - cursor); } }; class GreedyStrategy { public: [[nodiscard]] constexpr int operator ()(const auto id, const int cursor) const noexcept { const auto index = to_underlying(id); assert(std::cmp_less_equal(cursor, index)); return static_cast<int>(index) - cursor; } }; template <typename Id, auto priorityStrategy> class BasicSequenceInterface { template <typename... Sequences> friend class Config; public: using SequenceT = BasicSequence<Id, priorityStrategy>; ~BasicSequenceInterface() = default; [[nodiscard]] BasicSequenceInterface() = default; BasicSequenceInterface(const BasicSequenceInterface&) = delete; BasicSequenceInterface& operator =(const BasicSequenceInterface&) = delete; BasicSequenceInterface(BasicSequenceInterface&&) = delete; BasicSequenceInterface& operator =(BasicSequenceInterface&&) = delete; [[nodiscard]] constexpr Tag tag() const noexcept { return m_Sequence->tag(); } private: std::shared_ptr<SequenceT> m_Sequence{ std::make_shared<SequenceT>() }; }; template <typename... Sequences> class Config { template <typename... Ts> friend class Config; public: static constexpr std::size_t sequenceCount = sizeof...(Sequences); [[nodiscard]] Config() requires (0 == sizeof...(Sequences)) = default; template <typename... Interfaces> requires (sizeof...(Interfaces) == sequenceCount) [[nodiscard]] explicit constexpr Config(Interfaces&... interfaces) noexcept(1 == sequenceCount) : Config{ interfaces.m_Sequence... } { } [[nodiscard]] constexpr auto& sequences() const noexcept { return m_Sequences; } template <typename... OtherSequences> [[nodiscard]] constexpr Config<Sequences..., OtherSequences...> concat( const Config<OtherSequences...>& other ) const { return std::apply( [](auto... sequences) { return Config<Sequences..., OtherSequences...>{ std::move(sequences)... }; }, std::tuple_cat(m_Sequences, other.sequences())); } private: std::tuple<std::shared_ptr<Sequences>...> m_Sequences; [[nodiscard]] explicit constexpr Config(std::shared_ptr<Sequences>... sequences) noexcept(1 == sequenceCount) requires (0 < sequenceCount) { if constexpr (1 < sequenceCount) { std::array tags{ sequences->tag()... }; std::ranges::sort(tags); if (!std::ranges::empty(std::ranges::unique(tags))) { throw std::invalid_argument{ "Expectations can not be assigned to the same sequence multiple times." }; } } m_Sequences = std::tuple{ std::move(sequences)... }; } }; } } namespace mimicpp { /** * \brief The lazy sequence interface. * \ingroup EXPECTATION_SEQUENCE * \details This sequence type prefers to make the least possible sequence progress. * \note 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 LazySequence : public sequence::detail::BasicSequenceInterface< sequence::Id, sequence::detail::LazyStrategy{}> { }; /** * \brief The greedy sequence interface. * \ingroup EXPECTATION_SEQUENCE * \details This sequence type prefers to make the maximal possible sequence progress. * \note 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 GreedySequence : public sequence::detail::BasicSequenceInterface< sequence::Id, sequence::detail::GreedyStrategy{}> { }; /** * \brief The default sequence type (LazySequence). * \ingroup EXPECTATION_SEQUENCE */ using SequenceT = LazySequence; } namespace mimicpp::sequence::detail { [[nodiscard]] constexpr bool has_better_rating( const std::span<const rating> lhs, const std::span<const rating> rhs ) noexcept { int rating{}; for (const auto& [lhsPriority, lhsTag] : lhs) { if (const auto iter = std::ranges::find(rhs, lhsTag, &rating::tag); iter != std::ranges::end(rhs)) { rating += lhsPriority < iter->priority ? -1 : 1; } else { return true; } } return 0 <= rating; } } namespace mimicpp::expect { /** * \brief Attaches the expectation onto a sequence. * \ingroup EXPECTATION_SEQUENCE * \param sequence The sequence to be attached to. * \throws std::invalid_argument if the expectation is already part of the given sequence. * * \snippet Sequences.cpp sequence * \snippet Sequences.cpp sequence mixed */ template <typename Id, auto priorityStrategy> [[nodiscard]] constexpr auto in_sequence(sequence::detail::BasicSequenceInterface<Id, priorityStrategy>& sequence) noexcept { using ConfigT = sequence::detail::Config< sequence::detail::BasicSequence<Id, priorityStrategy>>; return ConfigT{ sequence }; } /** * \brief Attaches the expectation onto the listed sequences. * \ingroup EXPECTATION_SEQUENCE * \param firstSequence The first sequence to be attached to. * \param secondSequence The second sequence to be attached to. * \param otherSequences Other sequences to be attached to. * \throws std::invalid_argument if the expectation is already attached to any of the given sequences or the given sequences contain duplicates. * * \snippet Sequences.cpp sequence multiple sequences */ template < typename FirstId, auto firstPriorityStrategy, typename SecondId, auto secondPriorityStrategy, typename... OtherIds, auto... otherPriorityStrategies > [[nodiscard]] constexpr auto in_sequences( sequence::detail::BasicSequenceInterface<FirstId, firstPriorityStrategy>& firstSequence, sequence::detail::BasicSequenceInterface<SecondId, secondPriorityStrategy>& secondSequence, sequence::detail::BasicSequenceInterface<OtherIds, otherPriorityStrategies>&... otherSequences ) { using ConfigT = sequence::detail::Config< sequence::detail::BasicSequence<FirstId, firstPriorityStrategy>, sequence::detail::BasicSequence<SecondId, secondPriorityStrategy>, sequence::detail::BasicSequence<OtherIds, otherPriorityStrategies>...>; return ConfigT{ firstSequence, secondSequence, otherSequences... }; } } #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::custom { template <typename Matcher> struct matcher_traits; } namespace mimicpp::detail::matches_hook { template <typename Matcher, typename T> [[nodiscard]] constexpr bool matches_impl( const Matcher& matcher, T& target, [[maybe_unused]] const priority_tag<1> ) requires requires { { custom::matcher_traits<Matcher>{}.matches(matcher, target) } -> std::convertible_to<bool>; } { return custom::matcher_traits<Matcher>{}.matches(matcher, target); } template <typename Matcher, typename T> [[nodiscard]] constexpr bool matches_impl( const Matcher& matcher, T& target, [[maybe_unused]] const priority_tag<0> ) requires requires { { matcher.matches(target) } -> std::convertible_to<bool>; } { return matcher.matches(target); } constexpr priority_tag<1> maxTag; template <typename Matcher, typename T> [[nodiscard]] constexpr bool matches(const Matcher& matcher, T& target) requires requires { { matches_impl(matcher, target, maxTag) } -> std::convertible_to<bool>; } { return matches_impl(matcher, target, maxTag); } } namespace mimicpp::detail::describe_hook { template <typename Matcher> [[nodiscard]] constexpr decltype(auto) describe_impl( const Matcher& matcher, [[maybe_unused]] const priority_tag<1> ) requires requires { { custom::matcher_traits<Matcher>{}.describe(matcher) } -> std::convertible_to<StringViewT>; } { return custom::matcher_traits<Matcher>{}.describe(matcher); } template <typename Matcher> [[nodiscard]] constexpr decltype(auto) describe_impl( const Matcher& matcher, [[maybe_unused]] const priority_tag<0> ) requires requires { { matcher.describe() } -> std::convertible_to<StringViewT>; } { return matcher.describe(); } constexpr priority_tag<1> maxTag; template <typename Matcher> [[nodiscard]] constexpr decltype(auto) describe(const Matcher& matcher) requires requires { { describe_impl(matcher, maxTag) } -> std::convertible_to<StringViewT>; } { return describe_impl(matcher, maxTag); } } 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) { { detail::matches_hook::matches(matcher, target) } -> std::convertible_to<bool>; { detail::describe_hook::describe(matcher) } -> std::convertible_to<StringViewT>; }; /** * \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([[maybe_unused]] auto&& target) noexcept { return true; } static constexpr StringViewT describe() noexcept { return "has no constraints"; } }; } 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::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::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::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::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::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::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 { /** * \brief Tag type, used in string matchers. * \ingroup EXPECTATION_MATCHERS_STRING */ struct case_insensitive_t { } constexpr case_insensitive{}; } namespace mimicpp::matches::detail { template <string String> [[nodiscard]] constexpr auto make_view(String&& str) { using traits_t = string_traits<std::remove_cvref_t<String>>; return traits_t::view(std::forward<String>(str)); } template <case_foldable_string String> [[nodiscard]] constexpr auto make_case_folded_string(String&& str) { return std::invoke( string_case_fold_converter<string_char_t<String>>{}, detail::make_view(std::forward<String>(str))); } template <string Target, string Pattern> constexpr void check_string_compatibility() noexcept { static_assert( std::same_as< string_char_t<Target>, string_char_t<Pattern>>, "Pattern and target string must have the same character-type."); } } namespace mimicpp::matches::str { /** * \defgroup EXPECTATION_MATCHERS_STRING string matchers * \ingroup EXPECTATION_REQUIREMENT * \ingroup EXPECTATION_MATCHER * \brief String specific matchers. * \details These matchers are designed to work with any string- and character-type. * This comes with some caveats and restrictions, e.g. comparisons between strings of different character-types are not supported. * Any string, which satisfies the ``string`` concept is directly supported, thus it's possible to integrate your own types seamlessly. * * ## Example * * \snippet Requirements.cpp matcher str matcher * * ## Case-Insensitive Matchers * * In the following these terms are used: * - ``code-point`` is a logical string-element. In byte-strings these are the single characters, but in Unicode this may span multiple physical-elements. * - ``code-unit`` is the single physical-element of the string (i.e. the underlying character-type). Multiple ``code-units`` may build a single ``code-point``. * * All comparisons are done via iterators and, to make that consistent, ``mimic++`` requires the iterator values to be ``code-units``. * As this should be fine for equality-comparisons, this will quickly lead to issues when performing case-insensitive comparisons. * ``mimic++`` therefore converts all participating strings to their *case-folded* representation during comparisons. * * ### Case-Folding * * Case-Folding means the process of making a string independent of its case (e.g. ``a`` and ``A`` would compare equal). * This process is centralized to the ``string_case_fold_converter`` template, which must be specialized for the appropriate character-type. * The converter receives the whole string and is required to perform a consistent case-folding. Unicode defines the necessary steps here: * https://unicode-org.github.io/icu/userguide/transforms/casemappings.html#case-folding * * For a list of ``code-point`` mappings have a look here: * https://www.unicode.org/Public/UNIDATA/CaseFolding.txt * * Unfortunately, this requires a lot of work to make that work (correctly) for all existing character-types. * Due to this, currently only byte-strings are supported for case-insensitive comparisons. * * #### Byte-String * * Byte-Strings are element-wise lazily case-folded via ``std::toupper`` function. * * #### Strings with other character-types * * Even if ``mimic++`` does not support case-folding for other string types out of the box, users can specialize the ``string_case_fold_converter`` * for the missing character-types and thus inject the necessary support. * * Another, but yet experimental, possibility is to enable the \ref MIMICPP_CONFIG_EXPERIMENTAL_UNICODE_STR_MATCHER option. * *\{ */ /** * \brief Tests, whether the target string compares equal to the expected string. * \tparam Pattern The string type. * \param pattern The pattern object. */ template <string Pattern> [[nodiscard]] constexpr auto eq(Pattern&& pattern) { return PredicateMatcher{ []<string T, typename Stored>(T&& target, Stored&& stored) { detail::check_string_compatibility<T, Pattern>(); return std::ranges::equal( detail::make_view(std::forward<T>(target)), detail::make_view(std::forward<Stored>(stored))); }, "is equal to {}", "is not equal to {}", std::tuple{std::forward<Pattern>(pattern)} }; } /** * \brief Tests, whether the target string compares case-insensitively equal to the expected string. * \tparam Pattern The string type. * \param pattern The pattern object. */ template <case_foldable_string Pattern> [[nodiscard]] constexpr auto eq(Pattern&& pattern, [[maybe_unused]] const case_insensitive_t) { return PredicateMatcher{ []<case_foldable_string T, typename Stored>(T&& target, Stored&& stored) { detail::check_string_compatibility<T, Pattern>(); return std::ranges::equal( detail::make_case_folded_string(std::forward<T>(target)), detail::make_case_folded_string(std::forward<Stored>(stored))); }, "is case-insensitively equal to {}", "is case-insensitively not equal to {}", std::tuple{std::forward<Pattern>(pattern)} }; } /** * \brief Tests, whether the target string starts with the pattern string. * \tparam Pattern The string type. * \param pattern The pattern string. */ template <string Pattern> [[nodiscard]] constexpr auto starts_with(Pattern&& pattern) { return PredicateMatcher{ []<string T, typename Stored>(T&& target, Stored&& stored) { detail::check_string_compatibility<T, Pattern>(); auto patternView = detail::make_view(std::forward<Stored>(stored)); const auto [_, patternIter] = std::ranges::mismatch( detail::make_view(std::forward<T>(target)), patternView); return patternIter == std::ranges::end(patternView); }, "starts with {}", "starts not with {}", std::tuple{std::forward<Pattern>(pattern)} }; } /** * \brief Tests, whether the target string starts case-insensitively with the pattern string. * \tparam Pattern The string type. * \param pattern The pattern string. */ template <string Pattern> [[nodiscard]] constexpr auto starts_with(Pattern&& pattern, [[maybe_unused]] const case_insensitive_t) { return PredicateMatcher{ []<string T, typename Stored>(T&& target, Stored&& stored) { detail::check_string_compatibility<T, Pattern>(); auto caseFoldedPattern = detail::make_case_folded_string(std::forward<Stored>(stored)); const auto [_, patternIter] = std::ranges::mismatch( detail::make_case_folded_string(std::forward<T>(target)), caseFoldedPattern); return patternIter == std::ranges::end(caseFoldedPattern); }, "case-insensitively starts with {}", "case-insensitively starts not with {}", std::tuple{std::forward<Pattern>(pattern)} }; } /** * \brief Tests, whether the target string ends with the pattern string. * \tparam Pattern The string type. * \param pattern The pattern string. */ template <string Pattern> [[nodiscard]] constexpr auto ends_with(Pattern&& pattern) { return PredicateMatcher{ []<string T, typename Stored>(T&& target, Stored&& stored) { detail::check_string_compatibility<T, Pattern>(); auto patternView = detail::make_view(std::forward<Stored>(stored)) | std::views::reverse; const auto [_, patternIter] = std::ranges::mismatch( detail::make_view(std::forward<T>(target)) | std::views::reverse, patternView); return patternIter == std::ranges::end(patternView); }, "ends with {}", "ends not with {}", std::tuple{std::forward<Pattern>(pattern)} }; } /** * \brief Tests, whether the target string ends case-insensitively with the pattern string. * \tparam Pattern The string type. * \param pattern The pattern string. */ template <string Pattern> [[nodiscard]] constexpr auto ends_with(Pattern&& pattern, [[maybe_unused]] const case_insensitive_t) { return PredicateMatcher{ []<string T, typename Stored>(T&& target, Stored&& stored) { detail::check_string_compatibility<T, Pattern>(); auto caseFoldedPattern = detail::make_case_folded_string(std::forward<Stored>(stored)) | std::views::reverse; const auto [_, patternIter] = std::ranges::mismatch( detail::make_case_folded_string(std::forward<T>(target)) | std::views::reverse, caseFoldedPattern); return patternIter == std::ranges::end(caseFoldedPattern); }, "case-insensitively ends with {}", "case-insensitively ends not with {}", std::tuple{std::forward<Pattern>(pattern)} }; } /** * \brief Tests, whether the pattern string is part of the target string. * \tparam Pattern The string type. * \param pattern The pattern string. */ template <string Pattern> [[nodiscard]] constexpr auto contains(Pattern&& pattern) { return PredicateMatcher{ []<string T, typename Stored>(T&& target, Stored&& stored) { detail::check_string_compatibility<T, Pattern>(); auto patternView = detail::make_view(std::forward<Stored>(stored)); return std::ranges::empty(patternView) || !std::ranges::empty( std::ranges::search( detail::make_view(std::forward<T>(target)), std::move(patternView))); }, "contains {}", "contains not {}", std::tuple{std::forward<Pattern>(pattern)} }; } /** * \brief Tests, whether the pattern string is case-insensitively part of the target string. * \tparam Pattern The string type. * \param pattern The pattern string. */ template <string Pattern> [[nodiscard]] constexpr auto contains(Pattern&& pattern, [[maybe_unused]] const case_insensitive_t) { return PredicateMatcher{ []<string T, typename Stored>(T&& target, Stored&& stored) { detail::check_string_compatibility<T, Pattern>(); auto patternView = detail::make_case_folded_string(std::forward<Stored>(stored)); auto targetView = detail::make_case_folded_string(std::forward<T>(target)); return std::ranges::empty(patternView) || !std::ranges::empty(std::ranges::search(targetView, patternView)); }, "case-insensitively contains {}", "case-insensitively contains not {}", std::tuple{std::forward<Pattern>(pattern)} }; } /** * \} */ } 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: ControlPolicy.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_CONTROL_POLICY_HPP #define MIMICPP_CONTROL_POLICY_HPP #pragma once #include <cassert> #include <limits> #include <memory> #include <optional> #include <stdexcept> #include <tuple> #include <utility> #include <vector> namespace mimicpp::detail { template <typename... Sequences> [[nodiscard]] constexpr std::tuple<std::tuple<std::shared_ptr<Sequences>, sequence::Id>...> make_sequence_entries( const std::tuple<std::shared_ptr<Sequences>...>& sequences ) noexcept { // This is a workaround due to some issues with clang-17 with c++23 and libstdc++ // That configuration prevents the direct initialization, thus we have to default construct first and // setup afterwards. Compilers will probably detect that and optimize accordingly. std::tuple<std::tuple<std::shared_ptr<Sequences>, sequence::Id>...> result{}; std::invoke( [&]<std::size_t... indices>([[maybe_unused]] const std::index_sequence<indices...>) noexcept { ((std::get<indices>(result) = std::tuple{ std::get<indices>(sequences), std::get<indices>(sequences)->add(), }), ...); }, std::index_sequence_for<Sequences...>{}); return result; } class TimesConfig { public: [[nodiscard]] TimesConfig() = default; [[nodiscard]] constexpr TimesConfig(const int min, const int max) { if (min < 0 || max < 0 || max < min) { throw std::invalid_argument{ "min must be less or equal to max and both must not be less than zero." }; } m_Min = min; m_Max = max; } [[nodiscard]] constexpr int min() const noexcept { return m_Min; } [[nodiscard]] constexpr int max() const noexcept { return m_Max; } private: int m_Min{1}; int m_Max{1}; }; } namespace mimicpp { namespace detail { [[nodiscard]] control_state_t make_control_state( const int min, const int max, const int count, const auto& sequenceEntries ) { if (count == max) { return state_saturated{ .min = min, .max = max, .count = count, .sequences = std::apply( [](const auto&... entries) { return std::vector<sequence::Tag>{ std::get<0>(entries)->tag()... }; }, sequenceEntries) }; } std::vector<sequence::Tag> inapplicable{}; std::vector<sequence::rating> ratings{}; std::apply( [&](const auto&... entries) { const auto distribute = [&](auto& seq, const sequence::Id id) { if (const std::optional priority = seq->priority_of(id)) { ratings.emplace_back( *priority, seq->tag()); } else { inapplicable.emplace_back(seq->tag()); } }; (..., distribute( std::get<0>(entries), std::get<1>(entries))); }, sequenceEntries); if (!std::ranges::empty(inapplicable)) { return state_inapplicable{ .min = min, .max = max, .count = count, .sequenceRatings = std::move(ratings), .inapplicableSequences = std::move(inapplicable) }; } return state_applicable{ .min = min, .max = max, .count = count, .sequenceRatings = std::move(ratings), }; } } template <typename... Sequences> class ControlPolicy { public: static constexpr std::size_t sequenceCount{sizeof...(Sequences)}; [[nodiscard]] explicit constexpr ControlPolicy( const detail::TimesConfig& timesConfig, const sequence::detail::Config<Sequences...>& sequenceConfig ) noexcept : m_Min{timesConfig.min()}, m_Max{timesConfig.max()}, m_Sequences{ detail::make_sequence_entries(sequenceConfig.sequences()) } { update_sequence_states(); } [[nodiscard]] constexpr bool is_satisfied() const noexcept { return m_Min <= m_Count && m_Count <= m_Max; } [[nodiscard]] constexpr bool is_saturated() const noexcept { return m_Count == m_Max; } [[nodiscard]] constexpr bool is_applicable() const noexcept { return m_Count < m_Max && std::apply( [](const auto&... entries) noexcept { return (... && std::get<0>(entries)->is_consumable(std::get<1>(entries))); }, m_Sequences); } constexpr void consume() noexcept { assert(is_applicable()); std::apply( [](auto&... entries) noexcept { (..., std::get<0>(entries)->consume(std::get<1>(entries))); }, m_Sequences); ++m_Count; update_sequence_states(); } [[nodiscard]] control_state_t state() const { return detail::make_control_state( m_Min, m_Max, m_Count, m_Sequences); } private: int m_Min; int m_Max; int m_Count{}; std::tuple< std::tuple<std::shared_ptr<Sequences>, sequence::Id>...> m_Sequences{}; constexpr void update_sequence_states() noexcept { if (m_Count == m_Min) { std::apply( [](auto&... entries) noexcept { (..., std::get<0>(entries)->set_satisfied(std::get<1>(entries))); }, m_Sequences); } else if (m_Count == m_Max) { std::apply( [](auto&... entries) noexcept { (..., std::get<0>(entries)->set_saturated(std::get<1>(entries))); }, m_Sequences); } } }; } namespace mimicpp::expect { /** * \defgroup EXPECTATION_TIMES times * \ingroup EXPECTATION * \brief Times indicates, how often an expectation must be matched. * \details During each expectation building users can specify a times policy once. If not specified, that policy defaults to ``once``. * If users attempt to specify a times policy more than once for a single expectation, a compile-error will occur. * * Times in general have both, a lower and an upper limit. Both limits are treated as inclusive. * *\{ */ /** * \brief Specifies a times policy with a limit range. * \param min The lower limit. * \param max The upper limit. * \return The newly created policy. * \throws std::invalid_argument if * - ``min < 0``, * - ``max < 0`` or * - ``max < min``. * * \snippet Times.cpp times */ [[nodiscard]] constexpr auto times(const int min, const int max) { return detail::TimesConfig{min, max}; } /** * \brief Specifies a times policy with an exact limit. * \param exactly The limit. * \return The newly created policy. * \details This requires the expectation to be matched exactly the specified times. * \throws std::invalid_argument if ``exactly < 0``. * * \snippet Times.cpp times single */ [[nodiscard]] constexpr auto times(const int exactly) { return detail::TimesConfig(exactly, exactly); } /** * \brief Specifies a times policy with just a lower limit. * \tparam min The lower limit. * \return The newly created policy. * \details This requires the expectation to be matched at least ``min`` times or more. * \throws std::invalid_argument if ``min < 0``. * * \snippet Times.cpp at_least */ [[nodiscard]] constexpr auto at_least(const int min) { return detail::TimesConfig{ min, std::numeric_limits<int>::max() }; } /** * \brief Specifies a times policy with just an upper limit. * \param max The upper limit. * \return The newly created policy. * \details This requires the expectation to be matched up to ``max`` times. * \throws std::invalid_argument if ``max < 0``. * * \snippet Times.cpp at_most */ [[nodiscard]] constexpr auto at_most(const int max) { return detail::TimesConfig{ 0, max }; } /** * \brief Specifies a times policy with both limits set to 1. * \return The newly created policy. * \details This requires the expectation to be matched exactly once. * * \snippet Times.cpp once */ [[nodiscard]] consteval auto once() noexcept { constexpr detail::TimesConfig config{ 1, 1 }; return config; } /** * \brief Specifies a times policy with both limits set to 2. * \return The newly created policy. * \details This requires the expectation to be matched exactly twice. * * \snippet Times.cpp twice */ [[nodiscard]] consteval auto twice() noexcept { constexpr detail::TimesConfig config{ 2, 2 }; return config; } /** * \} */ } #endif /*** End of inlined file: ControlPolicy.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 <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; } template <typename Signature> constexpr auto pick_best_match(std::vector<std::tuple<Expectation<Signature>&, MatchReport>>& matches) { constexpr auto ratings = [](const auto& el) noexcept -> const auto& { return std::get<state_applicable>( std::get<MatchReport>(el).controlReport) .sequenceRatings; }; auto best = std::ranges::begin(matches); for (auto iter = best + 1; iter != std::ranges::end(matches); ++iter) { if (!sequence::detail::has_better_rating( ratings(*best), ratings(*iter))) { best = iter; } } return best; } } namespace mimicpp { /** * \defgroup EXPECTATION expectation * \brief Contains everything related to managing expectations. * \details Expectations are one of the two core aspects of ``mimic++``. They define how a ``Mock`` is expected to be called. * Users should always store expectations in the ``ScopedExpectation`` RAII-object, which checks whether the expectation is satisfied * during destruction. If not, an error is forwarded to the installed reporter. * To simplify that process, the macro \ref MIMICPP_SCOPED_EXPECTATION (and the shorthand \ref SCOPED_EXP) are provided. * * Once an expectation has been fully exhausted, it becomes inactive and can't be matched any further. * If at any time multiple active expectations would be valid matches for an incoming call, ``mimic++`` has to make a choice. * In such cases, when no ``Sequence`` has been applied, the latest created expectation will be selected. This may seem rather unintuitive at first, * but as expectations are bound to their scope, usually expectations within the current scope should be preferred over the expectations from the * outer scope. * * This is the technical explanation for the behavior, but users should not rely too much on that. * In fact, ``mimic++`` just makes the guarantee, that expectations from most inside scope will be preferred over expectations from further outside. * Users should think about expectations within the same scope as equally applicable and treat such ambiguities as undeterministic outcomes. * Nevertheless, ``mimic++`` provides a tool for such cases, which is designed to work in a deterministic manner: * \ref EXPECTATION_SEQUENCE "Sequences". * * Expectations can be created anywhere in the program, you just need an appropriate ``Mock``. * \{ */ /** * \brief The base interface for expectations. * \tparam Signature The decayed signature. */ template <typename Signature> requires std::same_as<Signature, signature_decay_t<Signature>> class Expectation { public: /** * \brief The expected call type. */ using CallInfoT = call::info_for_signature_t<Signature>; /** * \brief The return type. */ using ReturnT = signature_return_type_t<Signature>; /** * \brief Defaulted virtual destructor. */ virtual ~Expectation() = default; /** * \brief Defaulted default constructor. */ [[nodiscard]] Expectation() = default; /** * \brief Deleted copy-constructor. */ Expectation(const Expectation&) = delete; /** * \brief Deleted copy-assignment-operator. */ Expectation& operator =(const Expectation&) = delete; /** * \brief Deleted move-constructor. */ Expectation(Expectation&&) = delete; /** * \brief Deleted move-assignment-operator. */ Expectation& operator =(Expectation&&) = delete; /** * \brief Creates a report of the internal state. * \return A newly generated report. */ [[nodiscard]] virtual ExpectationReport report() const = 0; /** * \brief Queries all policies, whether they are satisfied. * \return Returns true, if all policies are satisfied. */ [[nodiscard]] virtual bool is_satisfied() const noexcept = 0; /** * \brief Queries all policies, whether they accept the given call. * \param call The call to be matched. * \return Returns true, if all policies accept the call. */ [[nodiscard]] virtual MatchReport matches(const CallInfoT& call) const = 0; /** * \brief Informs all policies, that the given call has been accepted. * \param call The call to be consumed. * \details This function is called, when a match has been made. */ virtual void consume(const CallInfoT& call) = 0; /** * \brief Requests the given call to be finalized. * \param call The call to be finalized. * \return Returns the call result. * \details This function is called, when a match has been made and ``consume`` has been called. */ [[nodiscard]] virtual constexpr ReturnT finalize_call(const CallInfoT& call) = 0; /** * \brief Returns the source-location, where this expectation has been created. * \return Immutable reference to the source-location. */ [[nodiscard]] virtual constexpr const std::source_location& from() const noexcept = 0; }; /** * \brief Collects all expectations for a specific (decayed) signature. * \tparam Signature The decayed signature. */ template <typename Signature> requires std::same_as<Signature, signature_decay_t<Signature>> class ExpectationCollection { public: /** * \brief The expected call type. */ using CallInfoT = call::info_for_signature_t<Signature>; /** * \brief The interface type of the stored expectations. */ using ExpectationT = Expectation<Signature>; /** * \brief The return type. */ using ReturnT = signature_return_type_t<Signature>; /** * \brief Defaulted destructor. */ ~ExpectationCollection() = default; /** * \brief Defaulted default constructor. */ [[nodiscard]] ExpectationCollection() = default; /** * \brief Deleted copy-constructor. */ ExpectationCollection(const ExpectationCollection&) = delete; /** * \brief Deleted copy-assignment-operator. */ ExpectationCollection& operator =(const ExpectationCollection&) = delete; /** * \brief Defaulted move-constructor. */ [[nodiscard]] ExpectationCollection(ExpectationCollection&&) = default; /** * \brief Defaulted move-assignment-operator. */ ExpectationCollection& operator =(ExpectationCollection&&) = default; /** * \brief Inserts the given expectation into the internal storage. * \param expectation The expectation to be inserted. * \attention Inserting an expectation, which is already element of any ExpectationCollection (including the current one), * is undefined behavior. */ 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)); } /** * \brief Removes the given expectation from the internal storage. * \param expectation The expectation to be removed. * \details This function also checks, whether the removed expectation is satisfied. If not, an * "unfulfilled expectation"- report is emitted. * \attention Removing an expectation, which is not element of the current ExpectationCollection, is undefined behavior. */ 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()); } } /** * \brief Handles the incoming call. * \param call The call to be handled. * \return Returns an appropriate result from the matched expectation. * \details This function queries all stored expectations, whether they accept the call. * If multiple matches are possible, the best match is selected and a "matched"-report is emitted. * If no matches are found, "no matched"-report is emitted and the call is aborted (e.g. by throwing an exception or terminating). * If matches are possible, but all expectations are saturated, an "inapplicable match"-report is emitted. */ [[nodiscard]] ReturnT handle_call(const CallInfoT& call) { std::vector<std::tuple<ExpectationT&, MatchReport>> matches{}; 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: matches.emplace_back(*exp, *std::move(matchReport)); break; // GCOVR_EXCL_START default: unreachable(); // GCOVR_EXCL_STOP } } } if (!std::ranges::empty(matches)) { auto&& [exp, report] = *detail::pick_best_match(matches); detail::report_full_match( make_call_report(call), std::move(report)); exp.consume(call); return exp.finalize_call(call); } 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{}; }; /** * \brief Determines, whether the given type satisfies the requirements of an expectation-policy for the given signature. */ 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) }; }; /** * \brief Determines, whether the given type satisfies the requirements of a finalize-policy for the given signature. */ 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>>; }; /** * \brief Determines, whether the given type satisfies the requirements of a control-policy. */ template <typename T> concept control_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).state() } -> std::convertible_to<control_state_t>; policy.consume(); }; /** * \brief The actual expectation template. * \tparam Signature The decayed signature. * \tparam ControlPolicy The applied control-policy. * \tparam FinalizePolicy The applied finalize-policy. * \tparam Policies All applied expectation-policies. */ template < typename Signature, control_policy ControlPolicy, finalize_policy_for<Signature> FinalizePolicy, expectation_policy_for<Signature>... Policies > class BasicExpectation final : public Expectation<Signature> { public: using ControlPolicyT = ControlPolicy; using FinalizerT = FinalizePolicy; using PolicyListT = std::tuple<Policies...>; using CallInfoT = call::info_for_signature_t<Signature>; using ReturnT = typename Expectation<Signature>::ReturnT; /** * \brief Constructs the expectation with the given arguments. * \tparam ControlPolicyArg The control-policy constructor argument types. * \tparam FinalizerArg The finalize-policy constructor argument types. * \tparam PolicyArgs The expectation-policies constructor argument types. * \param sourceLocation The source-location, where the construction has been requested from. * \param controlArg The control-policy constructor argument. * \param finalizerArg The finalize-policy constructor argument. * \param args The expectation-policies constructor arguments. */ template <typename ControlPolicyArg, typename FinalizerArg, typename... PolicyArgs> requires std::constructible_from<ControlPolicyT, ControlPolicyArg> && std::constructible_from<FinalizerT, FinalizerArg> && std::constructible_from<PolicyListT, PolicyArgs...> constexpr explicit BasicExpectation( const std::source_location& sourceLocation, ControlPolicyArg&& controlArg, FinalizerArg&& finalizerArg, PolicyArgs&&... args ) noexcept( std::is_nothrow_constructible_v<ControlPolicyT, ControlPolicyArg> && std::is_nothrow_constructible_v<FinalizerT, FinalizerArg> && (std::is_nothrow_constructible_v<Policies, PolicyArgs> && ...)) : m_SourceLocation{sourceLocation}, m_ControlPolicy{std::forward<ControlPolicyArg>(controlArg)}, m_Policies{std::forward<PolicyArgs>(args)...}, m_Finalizer{std::forward<FinalizerArg>(finalizerArg)} { } /** * \copydoc Expectation::report */ [[nodiscard]] ExpectationReport report() const override { return ExpectationReport{ .sourceLocation = m_SourceLocation, .finalizerDescription = std::nullopt, .timesDescription = std::invoke( [this] { StringStreamT ss{}; std::visit( std::bind_front( detail::control_state_printer{}, std::ostreambuf_iterator{ss}), m_ControlPolicy.state()); return std::move(ss).str(); }), .expectationDescriptions = std::apply( [&](const auto&... policies) { return std::vector<std::optional<StringT>>{ policies.describe()... }; }, m_Policies) }; } /** * \copydoc Expectation::is_satisfied */ [[nodiscard]] constexpr bool is_satisfied() const noexcept override { return m_ControlPolicy.is_satisfied() && std::apply( [](const auto&... policies) noexcept { return (... && policies.is_satisfied()); }, m_Policies); } /** * \copydoc Expectation::matches */ [[nodiscard]] MatchReport matches(const CallInfoT& call) const override { return MatchReport{ .sourceLocation = m_SourceLocation, .finalizeReport = {std::nullopt}, .controlReport = m_ControlPolicy.state(), .expectationReports = std::apply( [&](const auto&... policies) { return std::vector<MatchReport::Expectation>{ MatchReport::Expectation{ .isMatching = policies.matches(call), .description = policies.describe() }... }; }, m_Policies) }; } /** * \copydoc Expectation::consume */ constexpr void consume(const CallInfoT& call) override { m_ControlPolicy.consume(); std::apply( [&](auto&... policies) noexcept { (..., policies.consume(call)); }, m_Policies); } /** * \copydoc Expectation::finalize_call */ [[nodiscard]] constexpr ReturnT finalize_call(const CallInfoT& call) override { return m_Finalizer.finalize_call(call); } /** * \copydoc Expectation::from */ [[nodiscard]] constexpr const std::source_location& from() const noexcept override { return m_SourceLocation; } private: std::source_location m_SourceLocation; ControlPolicyT m_ControlPolicy; PolicyListT m_Policies; [[no_unique_address]] FinalizerT m_Finalizer{}; }; /** * \brief Takes the ownership of an expectation and check whether it's satisfied during destruction. * \details The owned Expectation is type-erased. This comes in handy, when users want to store ScopedExpectations * in a single container. */ class ScopedExpectation { private: class Concept { public: virtual ~Concept() noexcept(false) { } Concept(const Concept&) = delete; Concept& operator =(const Concept&) = delete; Concept(Concept&&) = delete; Concept& operator =(Concept&&) = delete; [[nodiscard]] virtual bool is_satisfied() const = 0; [[nodiscard]] virtual const std::source_location& from() const noexcept = 0; protected: Concept() = default; }; template <typename Signature> class Model final : public Concept { public: using StorageT = ExpectationCollection<Signature>; using ExpectationT = Expectation<Signature>; ~Model() noexcept(false) override { m_Storage->remove(m_Expectation); } [[nodiscard]] explicit Model( 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); } [[nodiscard]] bool is_satisfied() const override { return m_Expectation->is_satisfied(); } [[nodiscard]] const std::source_location& from() const noexcept override { return m_Expectation->from(); } private: std::shared_ptr<StorageT> m_Storage; std::shared_ptr<ExpectationT> m_Expectation; }; public: /** * \brief Removes the owned expectation from the ExpectationCollection and checks, whether it's satisfied. * \throws In cases of an unsatisfied expectation, the destructor is expected to throw of terminate otherwise. */ ~ScopedExpectation() noexcept(false) // NOLINT(modernize-use-equals-default) { // we must call the dtor manually here, because std::unique_ptr's dtor mustn't throw. delete m_Inner.release(); } /** * \brief Constructor, which generates the type-erase storage. * \tparam Signature The signature. * \param collection The expectation collection, the expectation will be attached to. * \param expectation The expectation. */ template <typename Signature> [[nodiscard]] explicit ScopedExpectation( std::shared_ptr<ExpectationCollection<Signature>> collection, std::shared_ptr<typename ExpectationCollection<Signature>::ExpectationT> expectation ) noexcept : m_Inner{ std::make_unique<Model<Signature>>( std::move(collection), std::move(expectation)) } { } /** * \brief A constructor, which accepts objects, which can be finalized (e.g. ExpectationBuilder). * \tparam T The object type. * \param object The object to be finalized. * \param loc The source-location. */ 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)} { } /** * \brief Deleted copy-constructor. */ ScopedExpectation(const ScopedExpectation&) = delete; /** * \brief Deleted copy-assignment-operator. */ ScopedExpectation& operator =(const ScopedExpectation&) = delete; /** * \brief Defaulted move-constructor. */ [[nodiscard]] ScopedExpectation(ScopedExpectation&&) = default; /** * \brief Defaulted move-assignment-operator. */ ScopedExpectation& operator =(ScopedExpectation&&) = default; /** * \brief Queries the stored expectation, whether it's satisfied. * \return True, if satisfied. */ [[nodiscard]] bool is_satisfied() const { return m_Inner->is_satisfied(); } /** * \brief Queries the stored expectation for it's stored source-location. * \return The stored source-location. */ [[nodiscard]] const std::source_location& from() const noexcept { return m_Inner->from(); } private: std::unique_ptr<Concept> m_Inner; }; /** * \} */ } #endif /*** End of inlined file: Expectation.hpp ***/ #include <cassert> #include <functional> namespace mimicpp::expectation_policies { class InitFinalize { public: template <typename Return, typename... Args> static constexpr void finalize_call(const call::Info<Return, Args...>&) noexcept { } }; 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 = {}, 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 { decltype(auto) projected = std::invoke(m_Projection, info); return detail::matches_hook::matches( m_Matcher, projected); } template <typename Return, typename... Args> static constexpr void consume([[maybe_unused]] const call::Info<Return, Args...>& info) noexcept { } [[nodiscard]] StringT describe() const { return std::invoke( m_Describer, detail::describe_hook::describe(m_Matcher)); } 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 = {} ) 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 = {} ) 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: [[no_unique_address]] Action m_Action; }; } namespace mimicpp::expect { 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. * *\{ */ /** * \brief Checks, whether the selected argument matches the given matcher. * \tparam Matcher The matcher type. * \param matcher The matcher. * \param projection Projection to apply to the argument. * * \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, typename Projection = std::identity> [[nodiscard]] constexpr auto arg( Matcher&& matcher, Projection projection = {} ) noexcept( std::is_nothrow_constructible_v<std::remove_cvref_t<Matcher>, Matcher> && std::is_nothrow_move_constructible_v<Projection>) { using ProjectionT = expectation_policies::ApplyArgsAction< Projection, std::add_lvalue_reference_t, index>; using PolicyT = expectation_policies::Requirement< std::remove_cvref_t<Matcher>, ProjectionT, detail::arg_requirement_describer<index>>; return PolicyT{ std::forward<Matcher>(matcher), ProjectionT{std::move(projection)} }; } /** * \} */ } 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>) { using ActionT = expectation_policies::ApplyArgsAction< std::remove_cvref_t<Action>, std::add_lvalue_reference_t, index, otherIndices...>; return expectation_policies::ReturnsResultOf{ ActionT{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>) { using ActionT = expectation_policies::ApplyAllArgsAction< std::remove_cvref_t<Action>, std::add_lvalue_reference_t>; return expectation_policies::ReturnsResultOf{ ActionT{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 { using ActionT = expectation_policies::ApplyArgsAction< std::identity, std::add_rvalue_reference_t, index>; return expectation_policies::ReturnsResultOf{ ActionT{} }; } /** * \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>) { using ActionT = expectation_policies::ApplyArgsAction< std::remove_cvref_t<Action>, std::add_lvalue_reference_t, index>; return expectation_policies::SideEffectAction{ ActionT{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>) { using ActionT = expectation_policies::ApplyArgsAction< std::remove_cvref_t<Action>, std::add_lvalue_reference_t, index, additionalIndices...>; return expectation_policies::SideEffectAction{ ActionT{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>) { using ActionT = expectation_policies::ApplyAllArgsAction< std::remove_cvref_t<Action>, std::add_lvalue_reference_t>; return expectation_policies::SideEffectAction{ ActionT{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 < bool timesConfigured, typename SequenceConfig, typename Signature, typename FinalizePolicy, expectation_policy_for<Signature>... Policies> class BasicExpectationBuilder { public: using StorageT = ExpectationCollection<Signature>; using PolicyListT = std::tuple<Policies...>; using ReturnT = typename Expectation<Signature>::ReturnT; ~BasicExpectationBuilder() = default; template <typename FinalizePolicyArg, typename PolicyListArg> requires std::constructible_from<FinalizePolicy, FinalizePolicyArg> && std::constructible_from<PolicyListT, PolicyListArg> [[nodiscard]] explicit constexpr BasicExpectationBuilder( std::shared_ptr<StorageT> storage, detail::TimesConfig timesConfig, SequenceConfig sequenceConfig, FinalizePolicyArg&& finalizePolicyArg, PolicyListArg&& policyListArg ) noexcept : m_Storage{std::move(storage)}, m_TimesConfig{std::move(timesConfig)}, m_SequenceConfig{std::move(sequenceConfig)}, 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::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< timesConfigured, SequenceConfig, Signature, std::remove_cvref_t<Policy>, Policies...>; return ExtendedExpectationBuilderT{ std::move(m_Storage), std::move(m_TimesConfig), std::move(m_SequenceConfig), 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) && // NOLINT(cppcoreguidelines-missing-std-forward) { using ExtendedExpectationBuilderT = BasicExpectationBuilder< timesConfigured, SequenceConfig, Signature, FinalizePolicy, Policies..., std::remove_cvref_t<Policy>>; return ExtendedExpectationBuilderT{ std::move(m_Storage), std::move(m_TimesConfig), std::move(m_SequenceConfig), 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 auto operator &&(detail::TimesConfig&& config) && requires (!timesConfigured) { using NewBuilderT = BasicExpectationBuilder< true, SequenceConfig, Signature, FinalizePolicy, Policies...>; return NewBuilderT{ std::move(m_Storage), std::move(config), std::move(m_SequenceConfig), std::move(m_FinalizePolicy), std::move(m_ExpectationPolicies) }; } template <typename... Sequences> [[nodiscard]] constexpr auto operator &&(sequence::detail::Config<Sequences...>&& config) && { sequence::detail::Config newConfig = m_SequenceConfig.concat(std::move(config)); using ExtendedExpectationBuilderT = BasicExpectationBuilder< timesConfigured, decltype(newConfig), Signature, FinalizePolicy, Policies...>; return ExtendedExpectationBuilderT{ std::move(m_Storage), std::move(m_TimesConfig), std::move(newConfig), std::move(m_FinalizePolicy), std::move(m_ExpectationPolicies) }; } [[nodiscard]] ScopedExpectation finalize(const std::source_location& sourceLocation) && { static_assert( finalize_policy_for<FinalizePolicy, Signature>, "For non-void return types, a finalize policy must be set."); return ScopedExpectation{ std::move(m_Storage), std::apply( [&](auto&... policies) { ControlPolicy controlPolicy{ std::move(m_TimesConfig), std::move(m_SequenceConfig) }; using ExpectationT = BasicExpectation< Signature, decltype(controlPolicy), FinalizePolicy, Policies...>; return std::make_unique<ExpectationT>( sourceLocation, std::move(controlPolicy), std::move(m_FinalizePolicy), std::move(policies)...); }, m_ExpectationPolicies) }; } private: std::shared_ptr<StorageT> m_Storage; detail::TimesConfig m_TimesConfig{}; SequenceConfig m_SequenceConfig{}; FinalizePolicy m_FinalizePolicy{}; PolicyListT m_ExpectationPolicies{}; }; } 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< false, sequence::detail::Config<>, Signature, expectation_policies::InitFinalize >; return detail::extend_builder_with_arg_policies<Signature>( BaseBuilderT{ std::move(expectations), TimesConfig{}, sequence::detail::Config<>{}, expectation_policies::InitFinalize{}, std::tuple{} }, std::index_sequence_for<Args...>{}, std::forward<Args>(args)...); } } #define MIMICPP_DETAIL_UNIQUE_NAME(prefix, counter) prefix##counter #define MIMICPP_DETAIL_SCOPED_EXPECTATION_IMPL(counter) \ [[maybe_unused]] \ const ::mimicpp::ScopedExpectation MIMICPP_DETAIL_UNIQUE_NAME(_mimicpp_expectation_, counter) = /** * \brief Convenience macro, which creates a ScopedExpectation with a unique name. * \ingroup MOCK */ #define MIMICPP_SCOPED_EXPECTATION MIMICPP_DETAIL_SCOPED_EXPECTATION_IMPL(__COUNTER__) #ifndef MIMICPP_CONFIG_ONLY_PREFIXED_MACROS /** * \brief Shorthand variant of \ref MIMICPP_SCOPED_EXPECTATION. * \ingroup MOCK */ #define SCOPED_EXP MIMICPP_SCOPED_EXPECTATION #endif #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 ) noexcept : m_Expectations{std::move(collection)} { } [[nodiscard]] 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...)> : protected 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)...); } protected: using SuperT::SuperT; }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) const> : protected 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)...); } protected: using SuperT::SuperT; }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) &> : protected 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)...); } protected: using SuperT::SuperT; }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) const &> : protected 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)...); } protected: using SuperT::SuperT; }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) &&> : protected 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)...); } protected: using SuperT::SuperT; }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) const &&> : protected 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)...); } protected: using SuperT::SuperT; }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) noexcept> : protected 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)...); } protected: using SuperT::SuperT; }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) const noexcept> : protected 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)...); } protected: using SuperT::SuperT; }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) & noexcept> : protected 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)...); } protected: using SuperT::SuperT; }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) const & noexcept> : protected 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)...); } protected: using SuperT::SuperT; }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) && noexcept> : protected 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)...); } protected: using SuperT::SuperT; }; template <typename Return, typename... Params> class MockFrontend<Return(Params...) const && noexcept> : protected 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)...); } protected: using SuperT::SuperT; }; template <typename List> struct expectation_collection_factory; template <typename... UniqueSignatures> struct expectation_collection_factory<std::tuple<UniqueSignatures...>> { [[nodiscard]] static auto make() { return std::tuple{ std::make_shared<ExpectationCollection<UniqueSignatures>>()... }; } }; } 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 Default constructor. */ [[nodiscard]] Mock() : Mock{ detail::expectation_collection_factory< detail::unique_list_t< signature_decay_t<FirstSignature>, signature_decay_t<OtherSignatures>...>>::make() } { } /** * \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; private: template <typename... Collections> explicit Mock(std::tuple<Collections...> collections) : detail::MockFrontend<FirstSignature>{ std::get<std::shared_ptr<ExpectationCollection<signature_decay_t<FirstSignature>>>>(collections) }, detail::MockFrontend<OtherSignatures>{ std::get<std::shared_ptr<ExpectationCollection<signature_decay_t<OtherSignatures>>>>(collections) }... { } }; /** * \} */ } #endif /*** End of inlined file: Mock.hpp ***/ /*** Start of inlined file: InterfaceMock.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_INTERFACE_MOCK_HPP #define MIMICPP_INTERFACE_MOCK_HPP #pragma once #include <type_traits> #include <utility> namespace mimicpp { /** * \defgroup MOCK_INTERFACES interfaces * \ingroup MOCK * \brief Contains utility to simplify interface mocking. * \details While this library tries avoiding macros when possible, sometimes we must not be too stubborn. * Making interface mocking more enjoyable is such a situation. While this can of course be done without macros, * this quickly becomes annoying, due to the necessary boilerplate code. * \snippet InterfaceMock.cpp interface mock manual * ``mimic++`` therefore introduces some macros, which helps to reduce the effort to a minimum. * With them, the boilerplate can be reduced to this macro invocation, which effectively does the same as before: * ```cpp * MOCK_METHOD(foo, void, ()); * ``` * * The good news is, that these macros are just a thin layer around the macro free core and can thus be easily avoided. * Nevertheless, ``mimic++`` still aims to become macro-less as possible. As soon as reflection becomes available, an * attempt will be made to solve this feature completely in c++ language (hopefully with c++26, but only time will tell). * * ## Multiple inheritance * Multiple inheritance is fully supported, without any special tricks. * \snippet InterfaceMock.cpp interface mock multiple inheritance */ /** * \defgroup MOCK_INTERFACES_DETAIL detail * \ingroup MOCK_INTERFACES * \brief Contains several macros, used for interface mock implementation. * \attention These macros should never be used directly by users. */ /** * \defgroup MOCK_INTERFACES_DETAIL_STRIP_PARENS strip_parens * \ingroup MOCK_INTERFACES_DETAIL * \brief Removes an enclosing pair of (), if present. */ } /** * \brief Removes an enclosing pair of (), if present. * \ingroup MOCK_INTERFACES_DETAIL_STRIP_PARENS * \param x token * \see Inspired by https://stackoverflow.com/a/62984543 */ #define MIMICPP_DETAIL_STRIP_PARENS(x) MIMICPP_DETAIL_STRIP_PARENS_OUTER(MIMICPP_DETAIL_STRIP_PARENS_INNER x) /** * \brief Black-magic. * \ingroup MOCK_INTERFACES_DETAIL_STRIP_PARENS */ #define MIMICPP_DETAIL_STRIP_PARENS_INNER(...) MIMICPP_DETAIL_STRIP_PARENS_INNER __VA_ARGS__ /** * \brief Black-magic. * \ingroup MOCK_INTERFACES_DETAIL_STRIP_PARENS */ #define MIMICPP_DETAIL_STRIP_PARENS_OUTER(...) MIMICPP_DETAIL_STRIP_PARENS_OUTER_(__VA_ARGS__) /** * \brief Black-magic. * \ingroup MOCK_INTERFACES_DETAIL_STRIP_PARENS */ #define MIMICPP_DETAIL_STRIP_PARENS_OUTER_(...) MIMICPP_DETAIL_STRIP_PARENS_STRIPPED_ ## __VA_ARGS__ /** * \brief Swallows the leftover token. * \ingroup MOCK_INTERFACES_DETAIL_STRIP_PARENS */ #define MIMICPP_DETAIL_STRIP_PARENS_STRIPPED_MIMICPP_DETAIL_STRIP_PARENS_INNER namespace mimicpp { /** * \defgroup MOCK_INTERFACES_DETAIL_FOR_EACH for_each * \ingroup MOCK_INTERFACES_DETAIL * \brief This is an implementation of a for-loop for the preprocessor. * \details This solution is highly inspired by the blog-article of David Mazieres. * He does a very good job in explaining the dark corners of the macro language, but even now, I do not * fully understand how this works. Either way, thank you very much! * \see https://www.scs.stanford.edu/~dm/blog/va-opt.html * \details All macros in this group are required to make that work. */ } /** * \brief Pastes a pair of parentheses. * \ingroup MOCK_INTERFACES_DETAIL_FOR_EACH */ #define MIMICPP_DETAIL_PARENS () /** * \brief Pastes a ,. * \ingroup MOCK_INTERFACES_DETAIL_FOR_EACH */ #define MIMICPP_DETAIL_COMMA_DELIMITER() , /** * \brief Pastes nothing. * \ingroup MOCK_INTERFACES_DETAIL_FOR_EACH */ #define MIMICPP_DETAIL_NO_DELIMITER() /** * \brief Pastes all arguments as provided. * \ingroup MOCK_INTERFACES_DETAIL_FOR_EACH */ #define MIMICPP_DETAIL_IDENTITY(...) __VA_ARGS__ /** * \brief Part of the fake recursion. * \ingroup MOCK_INTERFACES_DETAIL_FOR_EACH */ #define MIMICPP_DETAIL_EXPAND(...) MIMICPP_DETAIL_EXPAND3(MIMICPP_DETAIL_EXPAND3(MIMICPP_DETAIL_EXPAND3(MIMICPP_DETAIL_EXPAND3(__VA_ARGS__)))) /** * \brief Part of the fake recursion. * \ingroup MOCK_INTERFACES_DETAIL_FOR_EACH */ #define MIMICPP_DETAIL_EXPAND3(...) MIMICPP_DETAIL_EXPAND2(MIMICPP_DETAIL_EXPAND2(MIMICPP_DETAIL_EXPAND2(MIMICPP_DETAIL_EXPAND2(__VA_ARGS__)))) /** * \brief Part of the fake recursion. * \ingroup MOCK_INTERFACES_DETAIL_FOR_EACH */ #define MIMICPP_DETAIL_EXPAND2(...) MIMICPP_DETAIL_EXPAND1(MIMICPP_DETAIL_EXPAND1(MIMICPP_DETAIL_EXPAND1(MIMICPP_DETAIL_EXPAND1(__VA_ARGS__)))) /** * \brief Part of the fake recursion. * \ingroup MOCK_INTERFACES_DETAIL_FOR_EACH */ #define MIMICPP_DETAIL_EXPAND1(...) __VA_ARGS__ /** * \brief Calls the given macro with all other arguments. * \ingroup MOCK_INTERFACES_DETAIL_FOR_EACH * \param macro Macro to be called. * \param sequence First argument. * \param ... Accepts arbitrary arguments and forwards them. */ #define MIMICPP_DETAIL_FOR_EACH_EXT_INDIRECT(macro, sequence, ...) macro(sequence, __VA_ARGS__) /** * \brief The starting point of the for-each implementation. * \ingroup MOCK_INTERFACES_DETAIL_FOR_EACH * \param macro The strategy to be executed. * \param token A token, which will be expanded for each element. * \param delimiter The delimiter, which will added between element. * \param projection_macro The projection for the current element. * \param bound Addition data, which will be added to the call arguments. * * \details This is a very versatile implementation for the for-loop. * * During the development, it was necessary to generate unique names for function parameters, which I could also directly refer to. * That was the reason, why I've added the ``token`` argument. The first element will simply be called with the ``token`` content, but the second * with twice the token content and so on. It's ok, to provide an empty argument. */ #define MIMICPP_DETAIL_FOR_EACH_EXT(macro, token, delimiter, projection_macro, bound, ...) \ __VA_OPT__(MIMICPP_DETAIL_EXPAND(MIMICPP_DETAIL_FOR_EACH_EXT_HELPER(macro, token, token, delimiter, projection_macro, bound, __VA_ARGS__))) /** * \brief Black-magic. * \ingroup MOCK_INTERFACES_DETAIL_FOR_EACH */ #define MIMICPP_DETAIL_FOR_EACH_EXT_HELPER(macro, token, sequence, delimiter, projection_macro, bound, a1, ...) \ MIMICPP_DETAIL_FOR_EACH_EXT_INDIRECT(macro, sequence, MIMICPP_DETAIL_STRIP_PARENS(bound), projection_macro(a1)) \ __VA_OPT__(delimiter() MIMICPP_FOR_EACH_EXT_AGAIN MIMICPP_DETAIL_PARENS (macro, token, sequence##token, delimiter, projection_macro, bound, __VA_ARGS__)) /** * \brief Black-magic. * \ingroup MOCK_INTERFACES_DETAIL_FOR_EACH */ #define MIMICPP_FOR_EACH_EXT_AGAIN() MIMICPP_DETAIL_FOR_EACH_EXT_HELPER namespace mimicpp { /** * \defgroup MOCK_INTERFACES_DETAIL_MAKE_SIGNATURE_LIST make_signature_list * \ingroup MOCK_INTERFACES_DETAIL * \brief Converts all given arguments to a signature. */ } /** * \brief Converts the given information to a single signature. * \ingroup MOCK_INTERFACES_DETAIL_MAKE_SIGNATURE_LIST * \param sequence Unused. * \param bound_data Unused. * \param ret The return type. * \param param_type_list The parameter types. * \param specs Additional specs (e.g. ``const``, ``noexcept``). */ #define MIMICPP_DETAIL_MAKE_SIGNATURE(sequence, bound_data, ret, param_type_list, specs, ...) ret param_type_list specs /** * \brief Converts all given arguments to a signature list (not enclosed by parentheses). * \ingroup MOCK_INTERFACES_DETAIL_MAKE_SIGNATURE_LIST */ #define MIMICPP_DETAIL_MAKE_SIGNATURE_LIST(...) \ MIMICPP_DETAIL_FOR_EACH_EXT( \ MIMICPP_DETAIL_MAKE_SIGNATURE, \ , \ MIMICPP_DETAIL_COMMA_DELIMITER, \ MIMICPP_DETAIL_STRIP_PARENS, \ , \ __VA_ARGS__) /** * \brief Creates a mimicpp::Mock object for the given signatures. * \ingroup MOCK_INTERFACES_DETAIL * \param mock_name The mock name. * \param signatures The given signatures. Enclosing parentheses will be stripped. */ #define MIMICPP_DETAIL_MAKE_OVERLOADED_MOCK(mock_name, signatures) \ ::mimicpp::Mock< MIMICPP_DETAIL_STRIP_PARENS(signatures) > mock_name{} namespace mimicpp { /** * \defgroup MOCK_INTERFACES_DETAIL_MAKE_PARAM_LIST make_param_list * \ingroup MOCK_INTERFACES_DETAIL * \brief Converts all given arguments to a parameter-list. */ } /** * \brief Converts the given information to a single parameter. * \ingroup MOCK_INTERFACES_DETAIL_MAKE_PARAM_LIST * \param sequence A unique sequence, which will be appended to the parameter name (as suffix). * \param bound_data Unused. * \param type The type of the parameter. Enclosing parentheses will be stripped. */ #define MIMICPP_DETAIL_MAKE_PARAM(sequence, bound_data, type) MIMICPP_DETAIL_STRIP_PARENS(type) arg_##sequence /** * \brief Converts all given arguments to a parameter-list (not enclosed by parentheses). * \ingroup MOCK_INTERFACES_DETAIL_MAKE_PARAM_LIST */ #define MIMICPP_DETAIL_MAKE_PARAM_LIST(...) \ MIMICPP_DETAIL_FOR_EACH_EXT( \ MIMICPP_DETAIL_MAKE_PARAM, \ i, \ MIMICPP_DETAIL_COMMA_DELIMITER, \ MIMICPP_DETAIL_IDENTITY, \ , \ __VA_ARGS__) namespace mimicpp { /** * \defgroup MOCK_INTERFACES_DETAIL_FORWARD_ARGS forward_args * \ingroup MOCK_INTERFACES_DETAIL * \brief Creates ``std::forward`` calls for each given argument (not enclosed by parentheses). */ } /** * \brief Creates a ``std::forward`` call for the given argument. * \ingroup MOCK_INTERFACES_DETAIL_FORWARD_ARGS * \param sequence A unique sequence, which will be appended to the parameter name (as suffix). * \param bound_data Unused. * \param type The type of the parameter. Enclosing parentheses will be stripped. */ #define MIMICPP_DETAIL_FORWARD_ARG(sequence, bound_data, type) \ ::std::forward<::std::add_rvalue_reference_t<MIMICPP_DETAIL_STRIP_PARENS(type)>>(arg_##sequence) /** * \brief Creates ``std::forward`` calls for each given argument (not enclosed by parentheses). * \ingroup MOCK_INTERFACES_DETAIL_FORWARD_ARGS */ #define MIMICPP_DETAIL_FORWARD_ARGS(...) \ MIMICPP_DETAIL_FOR_EACH_EXT( \ MIMICPP_DETAIL_FORWARD_ARG, \ i, \ MIMICPP_DETAIL_COMMA_DELIMITER, \ MIMICPP_DETAIL_IDENTITY, \ , \ __VA_ARGS__) namespace mimicpp { /** * \defgroup MOCK_INTERFACES_DETAIL_MAKE_OVERLOAD_INFOS make_overload_infos * \ingroup MOCK_INTERFACES_DETAIL * \brief Related functions for MIMICPP_ADD_OVERLOAD. */ } /** * \brief Base overload, extending the overload info (enclosed by parentheses). * \ingroup MOCK_INTERFACES_DETAIL_MAKE_OVERLOAD_INFOS * \param ret The return type. * \param param_type_list The parameter types. * \param specs An optional parameter for categories (e.g. ``const``, ``noexcept``, etc.). */ #define MIMICPP_DETAIL_MAKE_OVERLOAD_INFOS_SPECS(ret, param_type_list, specs, ...) \ (ret, param_type_list, specs, \ (MIMICPP_DETAIL_MAKE_PARAM_LIST(MIMICPP_DETAIL_STRIP_PARENS(param_type_list))), \ (MIMICPP_DETAIL_FORWARD_ARGS(MIMICPP_DETAIL_STRIP_PARENS(param_type_list)))) /** * \brief Simple overload, extending the overload info (enclosed by parentheses). * \ingroup MOCK_INTERFACES_DETAIL_MAKE_OVERLOAD_INFOS * \param ret The return type. * \param param_type_list The parameter types. */ #define MIMICPP_DETAIL_MAKE_OVERLOAD_INFOS_BASIC(ret, param_type_list, ...) \ MIMICPP_DETAIL_MAKE_OVERLOAD_INFOS_SPECS(ret, param_type_list, ) /** * \brief Selects the correct overload, depending on the number of arguments. * \ingroup MOCK_INTERFACES_DETAIL_MAKE_OVERLOAD_INFOS * \see For an explanation of that pattern: https://stackoverflow.com/a/16683147 */ #define MIMICPP_DETAIL_SELECT_MAKE_OVERLOAD_INFOS(_1, N, ...) N /** * \brief Adds an overload to an interface mock. Used only in combination with \ref MIMICPP_MOCK_OVERLOADED_METHOD. * \ingroup MOCK_INTERFACES * \param ret The return type. * \param param_type_list The parameter types. * \param ... An optional parameter for categories (e.g. ``const``, ``noexcept``, etc.). */ #define MIMICPP_ADD_OVERLOAD(ret, param_type_list, ...) \ MIMICPP_DETAIL_SELECT_MAKE_OVERLOAD_INFOS( \ __VA_ARGS__, \ MIMICPP_DETAIL_MAKE_OVERLOAD_INFOS_SPECS, \ MIMICPP_DETAIL_MAKE_OVERLOAD_INFOS_BASIC)(ret, param_type_list, __VA_ARGS__,) // clangCl doesn't compile without that extra , namespace mimicpp { /** * \defgroup MOCK_INTERFACES_DETAIL_MAKE_METHOD_OVERRIDES make_method_overrides * \ingroup MOCK_INTERFACES_DETAIL * \brief Creates all required override overloads. */ } /** * \brief Create a single overload for the given information. * \ingroup MOCK_INTERFACES_DETAIL_MAKE_METHOD_OVERRIDES * \param ignore Ignored * \param mock_name The mock name. * \param fn_name The function name. * \param ret The return type. * \param param_type_list The parameter types. * \param specs Additional specifiers (e.g. ``const``, ``noexcept``, etc.). * \param param_list Enclosed parameter list. * \param forward_list Enclosed forward statements. */ #define MIMICPP_DETAIL_MAKE_METHOD_OVERRIDE(ignore, mock_name, fn_name, ret, param_type_list, specs, param_list, forward_list, ...) \ inline MIMICPP_DETAIL_STRIP_PARENS(ret) fn_name param_list MIMICPP_DETAIL_STRIP_PARENS(specs) override \ { \ return mock_name (MIMICPP_DETAIL_STRIP_PARENS(forward_list)); \ } /** * \brief Creates all overloads for a specific function as overrides. * \ingroup MOCK_INTERFACES_DETAIL_MAKE_METHOD_OVERRIDES * \param mock_name The mock name. * \param fn_name The function name to be overloaded. */ #define MIMICPP_DETAIL_MAKE_METHOD_OVERRIDES(mock_name, fn_name, ...) \ MIMICPP_DETAIL_FOR_EACH_EXT( \ MIMICPP_DETAIL_MAKE_METHOD_OVERRIDE, \ , \ MIMICPP_DETAIL_NO_DELIMITER, \ MIMICPP_DETAIL_STRIP_PARENS, \ (mock_name, fn_name), \ __VA_ARGS__) /** * \brief Starting point for mocking overloaded interface methods. * \ingroup MOCK_INTERFACES * \param fn_name The overload-set name. * \param ... Overloads must be added via \ref MIMICPP_ADD_OVERLOAD macro. * \details This macro creates a single mock object, which supports an arbitrary amount of overloads. * Those overloads will also be implemented as override methods, which all forward their calls to the * single mock object. * \snippet InterfaceMock.cpp interface mock overloaded */ #define MIMICPP_MOCK_OVERLOADED_METHOD(fn_name, ...) \ MIMICPP_DETAIL_MAKE_METHOD_OVERRIDES(fn_name##_, fn_name, __VA_ARGS__) \ MIMICPP_DETAIL_MAKE_OVERLOADED_MOCK( \ fn_name##_, \ (MIMICPP_DETAIL_MAKE_SIGNATURE_LIST(__VA_ARGS__))) /** * \brief Starting point for mocking a single interface method. * \ingroup MOCK_INTERFACES * \param fn_name The overload-set name. * \param param_type_list The parameter types. * \param ... An optional parameter for categories (e.g. ``const``, ``noexcept``, etc.). * \details This macro creates a single mock object with a single signature and creates a corresponding override method, * which forwards its calls to the mock object. * \snippet InterfaceMock.cpp interface mock simple */ #define MIMICPP_MOCK_METHOD(fn_name, ret, param_type_list, ...) \ MIMICPP_MOCK_OVERLOADED_METHOD( \ fn_name, \ MIMICPP_ADD_OVERLOAD(ret, param_type_list __VA_OPT__(,) __VA_ARGS__ )) #ifndef MIMICPP_CONFIG_ONLY_PREFIXED_MACROS /** * \brief Shorthand variant of \ref MIMICPP_MOCK_METHOD. * \ingroup MOCK_INTERFACES */ #define MOCK_METHOD MIMICPP_MOCK_METHOD /** * \brief Shorthand variant of \ref MIMICPP_MOCK_OVERLOADED_METHOD. * \ingroup MOCK_INTERFACES */ #define MOCK_OVERLOADED_METHOD MIMICPP_MOCK_OVERLOADED_METHOD /** * \brief Shorthand variant of \ref MIMICPP_ADD_OVERLOAD. * \ingroup MOCK_INTERFACES */ #define ADD_OVERLOAD MIMICPP_ADD_OVERLOAD #endif #endif /*** End of inlined file: InterfaceMock.hpp ***/ #endif
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 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 14.1.0
ARM GCC 14.1.0 (unknown-eabi)
ARM GCC 14.2.0
ARM GCC 14.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 (WINE)
ARM msvc v19.10 (WINE)
ARM msvc v19.14 (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 13.1.0
ARM64 gcc 13.2.0
ARM64 gcc 13.3.0
ARM64 gcc 14.1.0
ARM64 gcc 14.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 (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 13.1.0
AVR gcc 13.2.0
AVR gcc 13.3.0
AVR gcc 14.1.0
AVR gcc 14.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 gcc 13.1.0
BPF gcc 13.2.0
BPF gcc 13.3.0
BPF gcc trunk
EDG (experimental reflection)
EDG 6.5
EDG 6.5 (GNU mode gcc 13)
EDG 6.6
EDG 6.6 (GNU mode gcc 13)
FRC 2019
FRC 2020
FRC 2023
HPPA gcc 14.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)
LoongArch64 clang (trunk)
LoongArch64 clang 17.0.1
LoongArch64 clang 18.1.0
LoongArch64 clang 19.1.0
M68K gcc 13.1.0
M68K gcc 13.2.0
M68K gcc 13.3.0
M68K gcc 14.1.0
M68K gcc 14.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 13.1.0
RISC-V (32-bits) gcc 13.2.0
RISC-V (32-bits) gcc 13.3.0
RISC-V (32-bits) gcc 14.1.0
RISC-V (32-bits) gcc 14.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 13.1.0
RISC-V (64-bits) gcc 13.2.0
RISC-V (64-bits) gcc 13.3.0
RISC-V (64-bits) gcc 14.1.0
RISC-V (64-bits) gcc 14.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 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 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 13.1.0
SPARC LEON gcc 13.2.0
SPARC LEON gcc 13.3.0
SPARC LEON gcc 14.1.0
SPARC LEON gcc 14.2.0
SPARC gcc 12.2.0
SPARC gcc 12.3.0
SPARC gcc 12.4.0
SPARC gcc 13.1.0
SPARC gcc 13.2.0
SPARC gcc 13.3.0
SPARC gcc 14.1.0
SPARC gcc 14.2.0
SPARC64 gcc 12.2.0
SPARC64 gcc 12.3.0
SPARC64 gcc 12.4.0
SPARC64 gcc 13.1.0
SPARC64 gcc 13.2.0
SPARC64 gcc 13.3.0
SPARC64 gcc 14.1.0
SPARC64 gcc 14.2.0
TI C6x gcc 12.2.0
TI C6x gcc 12.3.0
TI C6x gcc 12.4.0
TI C6x gcc 13.1.0
TI C6x gcc 13.2.0
TI C6x gcc 13.3.0
TI C6x gcc 14.1.0
TI C6x gcc 14.2.0
TI CL430 21.6.1
VAX gcc NetBSDELF 10.4.0
VAX gcc NetBSDELF 10.5.0 (Nov 15 03:50:22 2023)
WebAssembly clang (trunk)
Xtensa ESP32 gcc 11.2.0 (2022r1)
Xtensa ESP32 gcc 12.2.0 (20230208)
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 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 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.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 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 9.0.0
armv8-a clang 9.0.1
clang-cl 18.1.0
ellcc 0.1.33
ellcc 0.1.34
ellcc 2017-07-16
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 13.1.0
loongarch64 gcc 13.2.0
loongarch64 gcc 13.3.0
loongarch64 gcc 14.1.0
loongarch64 gcc 14.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 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 13.1.0
mips gcc 13.2.0
mips gcc 13.3.0
mips gcc 14.1.0
mips gcc 14.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 13.1.0
mips64 (el) gcc 13.2.0
mips64 (el) gcc 13.3.0
mips64 (el) gcc 14.1.0
mips64 (el) gcc 14.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 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 13.1.0
mips64 gcc 13.2.0
mips64 gcc 13.3.0
mips64 gcc 14.1.0
mips64 gcc 14.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
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 gcc 12.1.0
mipsel gcc 12.2.0
mipsel gcc 12.3.0
mipsel gcc 12.4.0
mipsel gcc 13.1.0
mipsel gcc 13.2.0
mipsel gcc 13.3.0
mipsel gcc 14.1.0
mipsel gcc 14.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 13.1.0
power gcc 13.2.0
power gcc 13.3.0
power gcc 14.1.0
power gcc 14.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 13.1.0
power64 gcc 13.2.0
power64 gcc 13.3.0
power64 gcc 14.1.0
power64 gcc 14.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 13.1.0
power64le gcc 13.2.0
power64le gcc 13.3.0
power64le gcc 14.1.0
power64le gcc 14.2.0
power64le gcc 6.3.0
power64le gcc trunk
powerpc64 clang (trunk)
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 13.1.0
s390x gcc 13.2.0
s390x gcc 13.3.0
s390x gcc 14.1.0
s390x gcc 14.2.0
sh gcc 12.2.0
sh gcc 12.3.0
sh gcc 12.4.0
sh gcc 13.1.0
sh gcc 13.2.0
sh gcc 13.3.0
sh gcc 14.1.0
sh gcc 14.2.0
sh gcc 4.9.4
sh gcc 9.5.0
vast (trunk)
x64 msvc v19.0 (WINE)
x64 msvc v19.10 (WINE)
x64 msvc v19.14 (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.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 (WINE)
x86 msvc v19.10 (WINE)
x86 msvc v19.14 (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.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.3
x86 nvc++ 24.5
x86 nvc++ 24.7
x86-64 Zapcc 190308
x86-64 clang (EricWF contracts)
x86-64 clang (amd-staging)
x86-64 clang (assertions trunk)
x86-64 clang (clangir)
x86-64 clang (dascandy contracts)
x86-64 clang (experimental -Wlifetime)
x86-64 clang (experimental P1061)
x86-64 clang (experimental P1144)
x86-64 clang (experimental P1221)
x86-64 clang (experimental P2996)
x86-64 clang (experimental P3068)
x86-64 clang (experimental P3309)
x86-64 clang (experimental P3367)
x86-64 clang (experimental P3372)
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)
x86-64 clang (resugar)
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 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 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.4
x86-64 gcc 10.5
x86-64 gcc 11.1
x86-64 gcc 11.2
x86-64 gcc 11.3
x86-64 gcc 11.4
x86-64 gcc 12.1
x86-64 gcc 12.2
x86-64 gcc 12.3
x86-64 gcc 12.4
x86-64 gcc 13.1
x86-64 gcc 13.2
x86-64 gcc 13.3
x86-64 gcc 14.1
x86-64 gcc 14.2
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 (latest)
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
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.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 "catch_amalgamated.hpp" #include <iostream> // it's recommended to pull these sub-namespaces out, because then the expectation setup becomes much more readable namespace expect = mimicpp::expect; namespace matches = mimicpp::matches; namespace finally = mimicpp::finally; namespace then = mimicpp::then; using matches::_; // That's the wildcard matcher, which matches everything TEST_CASE("Mocks are function objects.") { mimicpp::Mock<int(std::string, std::optional<int>)> mock{}; // actually enables just `int operator ()(std::string, std::optional<int>)` SCOPED_EXP mock.expect_call("Hello, World", _) // requires the first argument to match the string "Hello, World"; the second has no restrictions and expect::at_least(1) // controls, how often the whole expectation must be matched and expect::arg<0>(!matches::range::is_empty()) // addtionally requires the first argument to be not empty (note the preceeding !) and expect::arg<1>(matches::ne(std::nullopt)) // requires the second argument to compare unequal to "std::nullopt" and expect::arg<1>(matches::lt(1337)) // and eventually to be less than 1337 and then::apply_arg<0>( // That's a side-effect, which get's executed, when a match has been made. [](std::string_view str) { std::cout << str << std::endl; }) // This one writes the content of the first argument to std::cout. and finally::returns(42); // And, when matches, returns 42 int result = mock("Hello, World", 1336); // matches REQUIRE(42 == result); } TEST_CASE("Mocks can be overloaded.") { mimicpp::Mock< int(std::string, std::optional<int>), // same as previous test void() const // enables `void operator ()() const` (note the const specification) > mock{}; SCOPED_EXP mock.expect_call() // setup an expectation for the void() overload and expect::twice(); // should be matched twice mock(); // first match // you can always create new expectations as you need them, even if the object is already in use SCOPED_EXP mock.expect_call(!matches::range::is_empty(), 42) // you can always apply matches directly; if just a value is provided, defaults to matches::eq and expect::once() // once() is the default, but you can state that explicitly and finally::throws(std::runtime_error{"some error"}); // when matches, throws an exception REQUIRE_THROWS(mock("Test", 42)); // ok, matched // still a pending expectation for void() overload std::as_const(mock)(); // explicitly call from a const object } TEST_CASE("Mocks can be used as member functions.") { // let's build a function, which actually expects an object and requires .get() member function, // This member function then should return something printable constexpr auto foo = [](const auto& obj) { std::cout << obj.get() << std::endl; }; struct Mock { mimicpp::Mock<int() const> get{}; // that serves as the .get() member function }; Mock mock{}; SCOPED_EXP mock.get.expect_call() and finally::returns(42); foo(mock); // fine, foo calls the get() member-function } TEST_CASE("Interfaces can be mocked.") { // let's say, we have the following interface class Interface { public: virtual ~Interface() = default; virtual int get() const = 0; }; // and a function, which this time actually requires an interface. constexpr auto foo = [](const Interface& obj) { std::cout << obj.get() << std::endl; }; class Derived : public Interface { public: ~Derived() override = default; // this generates the override method and a mock object named get_ MOCK_METHOD(get, int, (), const); }; Derived mock{}; SCOPED_EXP mock.get_.expect_call() // note the _ suffix. That's the name of the mock object. and finally::returns(42); foo(mock); // fine, foo calls the get() member-function, which forwards the call to the mock object get_. } TEST_CASE("Interface overload-sets are directly supported.") { // let's say, we have the following interface, with an overload-set class Interface { public: virtual ~Interface() = default; virtual int& get() = 0; virtual const int& get() const = 0; }; // and a function, using the const overload of that interface constexpr auto foo = [](const Interface& obj) { std::cout << obj.get() << std::endl; }; class Derived : public Interface { public: ~Derived() override = default; // this generates two overloads of get and a single mock object named get_ MOCK_OVERLOADED_METHOD( get, // the name of the overload-set ADD_OVERLOAD(int&, ()), // enables "int& operator ()()" on the mock ADD_OVERLOAD(const int&, (), const)); // enables "const int& operator ()() const" on the mock }; Derived mock{}; SCOPED_EXP std::as_const(mock).get_.expect_call() // As we expect the const overload to be used, we must explicitly select that overload. and finally::returns(42); // The returned reference is valid, as long as the expectation is alive. foo(mock); // fine, foo calls the get() const member-function, which forwards the call to the mock object get_ as before. }
Become a Patron
Sponsor on GitHub
Donate via PayPal
Source on GitHub
Mailing list
Installed libraries
Wiki
Report an issue
How it works
Contact the author
CE on Mastodon
About the author
Statistics
Changelog
Version tree