Merge branch 'master' into kimkulling/allow_empty_meshes_issue-797
commit
e7d0a8b975
|
@ -46,7 +46,7 @@ jobs:
|
||||||
toolchain: ninja-vs-win64-cxx17
|
toolchain: ninja-vs-win64-cxx17
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
|
@ -69,17 +69,11 @@ jobs:
|
||||||
|
|
||||||
- name: Checkout Hunter toolchains
|
- name: Checkout Hunter toolchains
|
||||||
if: endsWith(matrix.name, 'hunter')
|
if: endsWith(matrix.name, 'hunter')
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: cpp-pm/polly
|
repository: cpp-pm/polly
|
||||||
path: cmake/polly
|
path: cmake/polly
|
||||||
|
|
||||||
- name: Remove contrib directory for Hunter builds
|
|
||||||
if: contains(matrix.name, 'hunter')
|
|
||||||
uses: JesseTG/rm@v1.0.3
|
|
||||||
with:
|
|
||||||
path: contrib
|
|
||||||
|
|
||||||
- name: Cache DX SDK
|
- name: Cache DX SDK
|
||||||
id: dxcache
|
id: dxcache
|
||||||
if: contains(matrix.name, 'windows')
|
if: contains(matrix.name, 'windows')
|
||||||
|
|
|
@ -14,7 +14,7 @@ jobs:
|
||||||
name: adress-sanitizer
|
name: adress-sanitizer
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: lukka/get-cmake@latest
|
- uses: lukka/get-cmake@latest
|
||||||
- uses: lukka/set-shell-env@v1
|
- uses: lukka/set-shell-env@v1
|
||||||
with:
|
with:
|
||||||
|
@ -38,7 +38,7 @@ jobs:
|
||||||
name: undefined-behavior-sanitizer
|
name: undefined-behavior-sanitizer
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: lukka/get-cmake@latest
|
- uses: lukka/get-cmake@latest
|
||||||
- uses: lukka/set-shell-env@v1
|
- uses: lukka/set-shell-env@v1
|
||||||
with:
|
with:
|
||||||
|
@ -62,7 +62,7 @@ jobs:
|
||||||
name: printf-sanitizer
|
name: printf-sanitizer
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: run scan_printf script
|
- name: run scan_printf script
|
||||||
run: ./scripts/scan_printf.sh
|
run: ./scripts/scan_printf.sh
|
||||||
|
|
|
@ -49,14 +49,13 @@ option(ASSIMP_HUNTER_ENABLED "Enable Hunter package manager support" OFF)
|
||||||
IF(ASSIMP_HUNTER_ENABLED)
|
IF(ASSIMP_HUNTER_ENABLED)
|
||||||
include("cmake-modules/HunterGate.cmake")
|
include("cmake-modules/HunterGate.cmake")
|
||||||
HunterGate(
|
HunterGate(
|
||||||
URL "https://github.com/cpp-pm/hunter/archive/v0.24.17.tar.gz"
|
URL "https://github.com/cpp-pm/hunter/archive/v0.24.18.tar.gz"
|
||||||
SHA1 "e6396699e414120e32557fe92db097b7655b760b"
|
SHA1 "1292e4d661e1770d6d6ca08c12c07cf34a0bf718"
|
||||||
)
|
)
|
||||||
|
|
||||||
add_definitions(-DASSIMP_USE_HUNTER)
|
add_definitions(-DASSIMP_USE_HUNTER)
|
||||||
ENDIF()
|
ENDIF()
|
||||||
|
|
||||||
PROJECT(Assimp VERSION 5.2.5)
|
PROJECT(Assimp VERSION 5.3.0)
|
||||||
|
|
||||||
# All supported options ###############################################
|
# All supported options ###############################################
|
||||||
|
|
||||||
|
@ -201,12 +200,9 @@ SET (ASSIMP_VERSION ${ASSIMP_VERSION_MAJOR}.${ASSIMP_VERSION_MINOR}.${ASSIMP_VER
|
||||||
SET (ASSIMP_SOVERSION 5)
|
SET (ASSIMP_SOVERSION 5)
|
||||||
|
|
||||||
SET( ASSIMP_PACKAGE_VERSION "0" CACHE STRING "the package-specific version used for uploading the sources" )
|
SET( ASSIMP_PACKAGE_VERSION "0" CACHE STRING "the package-specific version used for uploading the sources" )
|
||||||
if(NOT ASSIMP_HUNTER_ENABLED)
|
|
||||||
# Enable C++17 support globally
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_C_STANDARD 99)
|
set(CMAKE_C_STANDARD 99)
|
||||||
endif()
|
|
||||||
|
|
||||||
IF(NOT ASSIMP_IGNORE_GIT_HASH)
|
IF(NOT ASSIMP_IGNORE_GIT_HASH)
|
||||||
# Get the current working branch
|
# Get the current working branch
|
||||||
|
@ -254,8 +250,7 @@ IF( UNIX )
|
||||||
# Use GNUInstallDirs for Unix predefined directories
|
# Use GNUInstallDirs for Unix predefined directories
|
||||||
INCLUDE(GNUInstallDirs)
|
INCLUDE(GNUInstallDirs)
|
||||||
# Ensure that we do not run into issues like http://www.tcm.phy.cam.ac.uk/sw/inodes64.html on 32 bit linux
|
# Ensure that we do not run into issues like http://www.tcm.phy.cam.ac.uk/sw/inodes64.html on 32 bit linux
|
||||||
IF( ${OPERATING_SYSTEM} MATCHES "Android")
|
IF(NOT ${OPERATING_SYSTEM} MATCHES "Android")
|
||||||
ELSE()
|
|
||||||
IF ( CMAKE_SIZEOF_VOID_P EQUAL 4) # only necessary for 32-bit linux
|
IF ( CMAKE_SIZEOF_VOID_P EQUAL 4) # only necessary for 32-bit linux
|
||||||
ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64 )
|
ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64 )
|
||||||
ENDIF()
|
ENDIF()
|
||||||
|
@ -263,9 +258,8 @@ IF( UNIX )
|
||||||
ENDIF()
|
ENDIF()
|
||||||
|
|
||||||
# Grouped compiler settings ########################################
|
# Grouped compiler settings ########################################
|
||||||
IF ((CMAKE_C_COMPILER_ID MATCHES "GNU") AND NOT MINGW)
|
IF ((CMAKE_C_COMPILER_ID MATCHES "GNU") AND NOT MINGW AND NOT HAIKU)
|
||||||
IF(NOT ASSIMP_HUNTER_ENABLED)
|
IF(NOT ASSIMP_HUNTER_ENABLED)
|
||||||
SET(CMAKE_CXX_STANDARD 17)
|
|
||||||
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
ENDIF()
|
ENDIF()
|
||||||
|
|
||||||
|
@ -302,7 +296,6 @@ ELSEIF(MSVC)
|
||||||
SET(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG:FULL /PDBALTPATH:%_PDB% /OPT:REF /OPT:ICF")
|
SET(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG:FULL /PDBALTPATH:%_PDB% /OPT:REF /OPT:ICF")
|
||||||
ELSEIF (CMAKE_CXX_COMPILER_ID MATCHES "Clang" )
|
ELSEIF (CMAKE_CXX_COMPILER_ID MATCHES "Clang" )
|
||||||
IF(NOT ASSIMP_HUNTER_ENABLED)
|
IF(NOT ASSIMP_HUNTER_ENABLED)
|
||||||
SET(CMAKE_CXX_STANDARD 17)
|
|
||||||
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
ENDIF()
|
ENDIF()
|
||||||
SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wall -Wno-long-long ${CMAKE_CXX_FLAGS}" )
|
SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -fno-strict-aliasing -Wall -Wno-long-long ${CMAKE_CXX_FLAGS}" )
|
||||||
|
@ -332,12 +325,12 @@ IF ( IOS AND NOT ASSIMP_HUNTER_ENABLED)
|
||||||
ELSE()
|
ELSE()
|
||||||
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -O3")
|
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fembed-bitcode -O3")
|
||||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -O3")
|
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fembed-bitcode -O3")
|
||||||
# Experimental for pdb generation
|
|
||||||
ENDIF()
|
ENDIF()
|
||||||
ENDIF()
|
ENDIF()
|
||||||
|
|
||||||
IF (ASSIMP_COVERALLS)
|
IF (ASSIMP_COVERALLS)
|
||||||
MESSAGE(STATUS "Coveralls enabled")
|
MESSAGE(STATUS "Coveralls enabled")
|
||||||
|
|
||||||
INCLUDE(Coveralls)
|
INCLUDE(Coveralls)
|
||||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
|
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
|
||||||
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
|
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
|
||||||
|
@ -345,12 +338,14 @@ ENDIF()
|
||||||
|
|
||||||
IF (ASSIMP_ASAN)
|
IF (ASSIMP_ASAN)
|
||||||
MESSAGE(STATUS "AddressSanitizer enabled")
|
MESSAGE(STATUS "AddressSanitizer enabled")
|
||||||
|
|
||||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
|
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
|
||||||
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
|
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address")
|
||||||
ENDIF()
|
ENDIF()
|
||||||
|
|
||||||
IF (ASSIMP_UBSAN)
|
IF (ASSIMP_UBSAN)
|
||||||
MESSAGE(STATUS "Undefined Behavior sanitizer enabled")
|
MESSAGE(STATUS "Undefined Behavior sanitizer enabled")
|
||||||
|
|
||||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin -fno-sanitize-recover=all")
|
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin -fno-sanitize-recover=all")
|
||||||
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin -fno-sanitize-recover=all")
|
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined,shift,shift-exponent,integer-divide-by-zero,unreachable,vla-bound,null,return,signed-integer-overflow,bounds,float-divide-by-zero,float-cast-overflow,nonnull-attribute,returns-nonnull-attribute,bool,enum,vptr,pointer-overflow,builtin -fno-sanitize-recover=all")
|
||||||
ENDIF()
|
ENDIF()
|
||||||
|
@ -697,7 +692,6 @@ ELSE()
|
||||||
COMPONENT ${LIBASSIMP_COMPONENT}
|
COMPONENT ${LIBASSIMP_COMPONENT}
|
||||||
INCLUDES DESTINATION include
|
INCLUDES DESTINATION include
|
||||||
)
|
)
|
||||||
|
|
||||||
ENDIF()
|
ENDIF()
|
||||||
ENDIF()
|
ENDIF()
|
||||||
ENDIF()
|
ENDIF()
|
||||||
|
@ -783,7 +777,7 @@ IF ( ASSIMP_INSTALL )
|
||||||
SET(CPACK_DEBIAN_PACKAGE_SECTION "libs" )
|
SET(CPACK_DEBIAN_PACKAGE_SECTION "libs" )
|
||||||
SET(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_COMPONENTS_ALL}")
|
SET(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_COMPONENTS_ALL}")
|
||||||
SET(CPACK_DEBIAN_PACKAGE_SUGGESTS)
|
SET(CPACK_DEBIAN_PACKAGE_SUGGESTS)
|
||||||
SET(cPACK_DEBIAN_PACKAGE_NAME "assimp")
|
SET(CPACK_DEBIAN_PACKAGE_NAME "assimp")
|
||||||
SET(CPACK_DEBIAN_PACKAGE_REMOVE_SOURCE_FILES contrib/gtest contrib/zlib workspaces test doc obj samples packaging)
|
SET(CPACK_DEBIAN_PACKAGE_REMOVE_SOURCE_FILES contrib/gtest contrib/zlib workspaces test doc obj samples packaging)
|
||||||
SET(CPACK_DEBIAN_PACKAGE_SOURCE_COPY svn export --force)
|
SET(CPACK_DEBIAN_PACKAGE_SOURCE_COPY svn export --force)
|
||||||
SET(CPACK_DEBIAN_CHANGELOG)
|
SET(CPACK_DEBIAN_CHANGELOG)
|
||||||
|
|
23
Readme.md
23
Readme.md
|
@ -6,20 +6,14 @@ Open Asset Import Library is a library to load various 3d file formats into a sh
|
||||||
### Current project status ###
|
### Current project status ###
|
||||||
[![Financial Contributors on Open Collective](https://opencollective.com/assimp/all/badge.svg?label=financial+contributors)](https://opencollective.com/assimp)
|
[![Financial Contributors on Open Collective](https://opencollective.com/assimp/all/badge.svg?label=financial+contributors)](https://opencollective.com/assimp)
|
||||||
![C/C++ CI](https://github.com/assimp/assimp/workflows/C/C++%20CI/badge.svg)
|
![C/C++ CI](https://github.com/assimp/assimp/workflows/C/C++%20CI/badge.svg)
|
||||||
<a href="https://scan.coverity.com/projects/5607">
|
|
||||||
<img alt="Coverity Scan Build Status"
|
|
||||||
src="https://scan.coverity.com/projects/5607/badge.svg"/>
|
|
||||||
</a>
|
|
||||||
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/9973693b7bdd4543b07084d5d9cf4745)](https://www.codacy.com/gh/assimp/assimp/dashboard?utm_source=github.com&utm_medium=referral&utm_content=assimp/assimp&utm_campaign=Badge_Grade)
|
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/9973693b7bdd4543b07084d5d9cf4745)](https://www.codacy.com/gh/assimp/assimp/dashboard?utm_source=github.com&utm_medium=referral&utm_content=assimp/assimp&utm_campaign=Badge_Grade)
|
||||||
|
|
||||||
[![Coverage Status](https://coveralls.io/repos/github/assimp/assimp/badge.svg?branch=master)](https://coveralls.io/github/assimp/assimp?branch=master)
|
|
||||||
[![Join the chat at https://gitter.im/assimp/assimp](https://badges.gitter.im/assimp/assimp.svg)](https://gitter.im/assimp/assimp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[![Join the chat at https://gitter.im/assimp/assimp](https://badges.gitter.im/assimp/assimp.svg)](https://gitter.im/assimp/assimp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/assimp/assimp.svg)](http://isitmaintained.com/project/assimp/assimp "Average time to resolve an issue")
|
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/assimp/assimp.svg)](http://isitmaintained.com/project/assimp/assimp "Average time to resolve an issue")
|
||||||
[![Percentage of issues still open](http://isitmaintained.com/badge/open/assimp/assimp.svg)](http://isitmaintained.com/project/assimp/assimp "Percentage of issues still open")
|
[![Percentage of issues still open](http://isitmaintained.com/badge/open/assimp/assimp.svg)](http://isitmaintained.com/project/assimp/assimp "Percentage of issues still open")
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
APIs are provided for C and C++. There are various bindings to other languages (C#, Java, Python, Delphi, D). Assimp also runs on Android and iOS.
|
APIs are provided for C and C++. There are various bindings to other languages (C#, Java, Python, Delphi, D). Assimp also runs on Android and iOS.
|
||||||
Additionally, assimp features various __mesh post processing tools__: normals and tangent space generation, triangulation, vertex cache locality optimization, removal of degenerate primitives and duplicate vertices, sorting by primitive type, merging of redundant materials and many more.
|
Additionally, assimp features various __mesh post-processing tools__: normals and tangent space generation, triangulation, vertex cache locality optimization, removal of degenerate primitives and duplicate vertices, sorting by primitive type, merging of redundant materials and many more.
|
||||||
|
|
||||||
### Latest Doc's ###
|
### Latest Doc's ###
|
||||||
Please check the latest documents at [Asset-Importer-Lib-Doc](https://assimp-docs.readthedocs.io/en/latest/).
|
Please check the latest documents at [Asset-Importer-Lib-Doc](https://assimp-docs.readthedocs.io/en/latest/).
|
||||||
|
@ -58,20 +52,21 @@ Take a look into the https://github.com/assimp/assimp/blob/master/Build.md file.
|
||||||
|
|
||||||
### Other tools ###
|
### Other tools ###
|
||||||
[open3mod](https://github.com/acgessler/open3mod) is a powerful 3D model viewer based on Assimp's import and export abilities.
|
[open3mod](https://github.com/acgessler/open3mod) is a powerful 3D model viewer based on Assimp's import and export abilities.
|
||||||
|
[Assimp-Viewer(]https://github.com/assimp/assimp_view) is an experimental implementation for an Asset-Viewer based on ImGUI and Assimp (experimental).
|
||||||
|
|
||||||
#### Repository structure ####
|
#### Repository structure ####
|
||||||
Open Asset Import Library is implemented in C++. The directory structure looks like:
|
Open Asset Import Library is implemented in C++. The directory structure looks like this:
|
||||||
|
|
||||||
/code Source code
|
/code Source code
|
||||||
/contrib Third-party libraries
|
/contrib Third-party libraries
|
||||||
/doc Documentation (doxysource and pre-compiled docs)
|
/doc Documentation (doxysource and pre-compiled docs)
|
||||||
/fuzz Contains the test-code for the Google-Fuzzer project
|
/fuzz Contains the test code for the Google Fuzzer project
|
||||||
/include Public header C and C++ header files
|
/include Public header C and C++ header files
|
||||||
/scripts Scripts used to generate the loading code for some formats
|
/scripts Scripts are used to generate the loading code for some formats
|
||||||
/port Ports to other languages and scripts to maintain those.
|
/port Ports to other languages and scripts to maintain those.
|
||||||
/test Unit- and regression tests, test suite of models
|
/test Unit- and regression tests, test suite of models
|
||||||
/tools Tools (old assimp viewer, command line `assimp`)
|
/tools Tools (old assimp viewer, command line `assimp`)
|
||||||
/samples A small number of samples to illustrate possible use-cases for Assimp
|
/samples A small number of samples to illustrate possible use cases for Assimp
|
||||||
|
|
||||||
The source code is organized in the following way:
|
The source code is organized in the following way:
|
||||||
|
|
||||||
|
@ -79,9 +74,9 @@ The source code is organized in the following way:
|
||||||
code/CApi Special implementations which are only used for the C-API
|
code/CApi Special implementations which are only used for the C-API
|
||||||
code/Geometry A collection of geometry tools
|
code/Geometry A collection of geometry tools
|
||||||
code/Material The material system
|
code/Material The material system
|
||||||
code/PBR An exporter for physical based models
|
code/PBR An exporter for physical-based models
|
||||||
code/PostProcessing The post-processing steps
|
code/PostProcessing The post-processing steps
|
||||||
code/AssetLib/<FormatName> Implementation for import and export for the format
|
code/AssetLib/<FormatName> Implementation for import and export of the format
|
||||||
|
|
||||||
### Contributing ###
|
### Contributing ###
|
||||||
Contributions to assimp are highly appreciated. The easiest way to get involved is to submit
|
Contributions to assimp are highly appreciated. The easiest way to get involved is to submit
|
||||||
|
@ -118,4 +113,4 @@ and don't sue us if our code doesn't work. Note that, unlike LGPLed code, you ma
|
||||||
For the legal details, see the `LICENSE` file.
|
For the legal details, see the `LICENSE` file.
|
||||||
|
|
||||||
### Why this name ###
|
### Why this name ###
|
||||||
Sorry, we're germans :-), no english native speakers ...
|
Sorry, we're germans :-), no English native speakers ...
|
||||||
|
|
|
@ -102,10 +102,6 @@ void Structure::Convert<CollectionObject>(
|
||||||
|
|
||||||
ReadFieldPtr<ErrorPolicy_Fail>(dest.next, "*next", db);
|
ReadFieldPtr<ErrorPolicy_Fail>(dest.next, "*next", db);
|
||||||
{
|
{
|
||||||
//std::shared_ptr<CollectionObject> prev;
|
|
||||||
//ReadFieldPtr<ErrorPolicy_Fail>(prev, "*prev", db);
|
|
||||||
//dest.prev = prev.get();
|
|
||||||
|
|
||||||
std::shared_ptr<Object> ob;
|
std::shared_ptr<Object> ob;
|
||||||
ReadFieldPtr<ErrorPolicy_Igno>(ob, "*ob", db);
|
ReadFieldPtr<ErrorPolicy_Igno>(ob, "*ob", db);
|
||||||
dest.ob = ob.get();
|
dest.ob = ob.get();
|
||||||
|
|
|
@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
|
||||||
|
|
||||||
Copyright (c) 2006-2022, assimp team
|
Copyright (c) 2006-2022, assimp team
|
||||||
|
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use of this software in source and binary forms,
|
Redistribution and use of this software in source and binary forms,
|
||||||
|
@ -40,10 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @file BlenderTessellator.cpp
|
/// @file BlenderTessellator.cpp
|
||||||
* @brief A simple tessellation wrapper
|
/// @brief A simple tessellation wrapper
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER
|
#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
|
||||||
|
|
||||||
Copyright (c) 2006-2022, assimp team
|
Copyright (c) 2006-2022, assimp team
|
||||||
|
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use of this software in source and binary forms,
|
Redistribution and use of this software in source and binary forms,
|
||||||
|
@ -144,11 +143,7 @@ namespace Assimp
|
||||||
|
|
||||||
#if ASSIMP_BLEND_WITH_POLY_2_TRI
|
#if ASSIMP_BLEND_WITH_POLY_2_TRI
|
||||||
|
|
||||||
#ifdef ASSIMP_USE_HUNTER
|
#include "contrib/poly2tri/poly2tri/poly2tri.h"
|
||||||
# include <poly2tri/poly2tri.h>
|
|
||||||
#else
|
|
||||||
# include "../contrib/poly2tri/poly2tri/poly2tri.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace Assimp
|
namespace Assimp
|
||||||
{
|
{
|
||||||
|
|
|
@ -731,7 +731,7 @@ void DXFImporter::ParsePolyLineVertex(DXF::LineReader& reader, DXF::PolyLine& li
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
indices[cnti++] = static_cast<unsigned int>(index);
|
indices[cnti++] = static_cast<unsigned int>(index);
|
||||||
} else {
|
} else {
|
||||||
ASSIMP_LOG_WARN("DXF: Skip invisible face.");
|
indices[cnti++] = static_cast<unsigned int>(-index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -51,6 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
#include "FBXUtil.h"
|
#include "FBXUtil.h"
|
||||||
#include <assimp/defs.h>
|
#include <assimp/defs.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <cstdint>
|
||||||
#include <assimp/Exceptional.h>
|
#include <assimp/Exceptional.h>
|
||||||
#include <assimp/ByteSwapper.h>
|
#include <assimp/ByteSwapper.h>
|
||||||
#include <assimp/DefaultLogger.hpp>
|
#include <assimp/DefaultLogger.hpp>
|
||||||
|
|
|
@ -577,16 +577,17 @@ void FBXConverter::GetRotationMatrix(Model::RotOrder mode, const aiVector3D &rot
|
||||||
bool is_id[3] = { true, true, true };
|
bool is_id[3] = { true, true, true };
|
||||||
|
|
||||||
aiMatrix4x4 temp[3];
|
aiMatrix4x4 temp[3];
|
||||||
if (std::fabs(rotation.z) > angle_epsilon) {
|
const auto rot = AI_DEG_TO_RAD(rotation);
|
||||||
aiMatrix4x4::RotationZ(AI_DEG_TO_RAD(rotation.z), temp[2]);
|
if (std::fabs(rot.z) > angle_epsilon) {
|
||||||
|
aiMatrix4x4::RotationZ(rot.z, temp[2]);
|
||||||
is_id[2] = false;
|
is_id[2] = false;
|
||||||
}
|
}
|
||||||
if (std::fabs(rotation.y) > angle_epsilon) {
|
if (std::fabs(rot.y) > angle_epsilon) {
|
||||||
aiMatrix4x4::RotationY(AI_DEG_TO_RAD(rotation.y), temp[1]);
|
aiMatrix4x4::RotationY(rot.y, temp[1]);
|
||||||
is_id[1] = false;
|
is_id[1] = false;
|
||||||
}
|
}
|
||||||
if (std::fabs(rotation.x) > angle_epsilon) {
|
if (std::fabs(rot.x) > angle_epsilon) {
|
||||||
aiMatrix4x4::RotationX(AI_DEG_TO_RAD(rotation.x), temp[0]);
|
aiMatrix4x4::RotationX(rot.x, temp[0]);
|
||||||
is_id[0] = false;
|
is_id[0] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3225,7 +3226,6 @@ aiNodeAnim* FBXConverter::GenerateSimpleNodeAnim(const std::string& name,
|
||||||
aiVector3D defTranslate = PropertyGet(props, "Lcl Translation", aiVector3D(0.f, 0.f, 0.f));
|
aiVector3D defTranslate = PropertyGet(props, "Lcl Translation", aiVector3D(0.f, 0.f, 0.f));
|
||||||
aiVector3D defRotation = PropertyGet(props, "Lcl Rotation", aiVector3D(0.f, 0.f, 0.f));
|
aiVector3D defRotation = PropertyGet(props, "Lcl Rotation", aiVector3D(0.f, 0.f, 0.f));
|
||||||
aiVector3D defScale = PropertyGet(props, "Lcl Scaling", aiVector3D(1.f, 1.f, 1.f));
|
aiVector3D defScale = PropertyGet(props, "Lcl Scaling", aiVector3D(1.f, 1.f, 1.f));
|
||||||
aiQuaternion defQuat = EulerToQuaternion(defRotation, rotOrder);
|
|
||||||
|
|
||||||
aiVectorKey* outTranslations = new aiVectorKey[keyCount];
|
aiVectorKey* outTranslations = new aiVectorKey[keyCount];
|
||||||
aiQuatKey* outRotations = new aiQuatKey[keyCount];
|
aiQuatKey* outRotations = new aiQuatKey[keyCount];
|
||||||
|
@ -3241,8 +3241,9 @@ aiNodeAnim* FBXConverter::GenerateSimpleNodeAnim(const std::string& name,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyframeLists[TransformationComp_Rotation].size() > 0) {
|
if (keyframeLists[TransformationComp_Rotation].size() > 0) {
|
||||||
InterpolateKeys(outRotations, keytimes, keyframeLists[TransformationComp_Rotation], defRotation, maxTime, minTime, rotOrder);
|
InterpolateKeys(outRotations, keytimes, keyframeLists[TransformationComp_Rotation], AI_DEG_TO_RAD(defRotation), maxTime, minTime, rotOrder);
|
||||||
} else {
|
} else {
|
||||||
|
aiQuaternion defQuat = EulerToQuaternion(AI_DEG_TO_RAD(defRotation), rotOrder);
|
||||||
for (size_t i = 0; i < keyCount; ++i) {
|
for (size_t i = 0; i < keyCount; ++i) {
|
||||||
outRotations[i].mTime = CONVERT_FBX_TIME(keytimes[i]) * anim_fps;
|
outRotations[i].mTime = CONVERT_FBX_TIME(keytimes[i]) * anim_fps;
|
||||||
outRotations[i].mValue = defQuat;
|
outRotations[i].mValue = defQuat;
|
||||||
|
@ -3264,7 +3265,7 @@ aiNodeAnim* FBXConverter::GenerateSimpleNodeAnim(const std::string& name,
|
||||||
|
|
||||||
const aiVector3D& preRotation = PropertyGet<aiVector3D>(props, "PreRotation", ok);
|
const aiVector3D& preRotation = PropertyGet<aiVector3D>(props, "PreRotation", ok);
|
||||||
if (ok && preRotation.SquareLength() > zero_epsilon) {
|
if (ok && preRotation.SquareLength() > zero_epsilon) {
|
||||||
const aiQuaternion preQuat = EulerToQuaternion(preRotation, Model::RotOrder_EulerXYZ);
|
const aiQuaternion preQuat = EulerToQuaternion(AI_DEG_TO_RAD(preRotation), Model::RotOrder_EulerXYZ);
|
||||||
for (size_t i = 0; i < keyCount; ++i) {
|
for (size_t i = 0; i < keyCount; ++i) {
|
||||||
outRotations[i].mValue = preQuat * outRotations[i].mValue;
|
outRotations[i].mValue = preQuat * outRotations[i].mValue;
|
||||||
}
|
}
|
||||||
|
@ -3272,7 +3273,7 @@ aiNodeAnim* FBXConverter::GenerateSimpleNodeAnim(const std::string& name,
|
||||||
|
|
||||||
const aiVector3D& postRotation = PropertyGet<aiVector3D>(props, "PostRotation", ok);
|
const aiVector3D& postRotation = PropertyGet<aiVector3D>(props, "PostRotation", ok);
|
||||||
if (ok && postRotation.SquareLength() > zero_epsilon) {
|
if (ok && postRotation.SquareLength() > zero_epsilon) {
|
||||||
const aiQuaternion postQuat = EulerToQuaternion(postRotation, Model::RotOrder_EulerXYZ);
|
const aiQuaternion postQuat = EulerToQuaternion(AI_DEG_TO_RAD(postRotation), Model::RotOrder_EulerXYZ);
|
||||||
for (size_t i = 0; i < keyCount; ++i) {
|
for (size_t i = 0; i < keyCount; ++i) {
|
||||||
outRotations[i].mValue = outRotations[i].mValue * postQuat;
|
outRotations[i].mValue = outRotations[i].mValue * postQuat;
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,8 +74,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
// https://code.blender.org/2013/08/fbx-binary-file-format-specification/
|
// https://code.blender.org/2013/08/fbx-binary-file-format-specification/
|
||||||
// https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure
|
// https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure
|
||||||
|
|
||||||
const ai_real DEG = ai_real( 57.29577951308232087679815481 ); // degrees per radian
|
|
||||||
|
|
||||||
using namespace Assimp;
|
using namespace Assimp;
|
||||||
using namespace Assimp::FBX;
|
using namespace Assimp::FBX;
|
||||||
|
|
||||||
|
@ -1391,7 +1389,7 @@ void FBXExporter::WriteObjects ()
|
||||||
aiMaterial* m = mScene->mMaterials[i];
|
aiMaterial* m = mScene->mMaterials[i];
|
||||||
|
|
||||||
// these are used to receive material data
|
// these are used to receive material data
|
||||||
float f; aiColor3D c;
|
ai_real f; aiColor3D c;
|
||||||
|
|
||||||
// start the node record
|
// start the node record
|
||||||
FBX::Node n("Material");
|
FBX::Node n("Material");
|
||||||
|
@ -2434,7 +2432,7 @@ void FBXExporter::WriteObjects ()
|
||||||
aiMatrix4x4 m(k.mValue.GetMatrix());
|
aiMatrix4x4 m(k.mValue.GetMatrix());
|
||||||
aiVector3D qs, qr, qt;
|
aiVector3D qs, qr, qt;
|
||||||
m.Decompose(qs, qr, qt);
|
m.Decompose(qs, qr, qt);
|
||||||
qr *= DEG;
|
qr = AI_RAD_TO_DEG(qr);
|
||||||
xval.push_back(qr.x);
|
xval.push_back(qr.x);
|
||||||
yval.push_back(qr.y);
|
yval.push_back(qr.y);
|
||||||
zval.push_back(qr.z);
|
zval.push_back(qr.z);
|
||||||
|
@ -2515,9 +2513,10 @@ void FBXExporter::WriteModelNode(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (r != zero) {
|
if (r != zero) {
|
||||||
|
r = AI_RAD_TO_DEG(r);
|
||||||
p.AddP70(
|
p.AddP70(
|
||||||
"Lcl Rotation", "Lcl Rotation", "", "A",
|
"Lcl Rotation", "Lcl Rotation", "", "A",
|
||||||
double(DEG*r.x), double(DEG*r.y), double(DEG*r.z)
|
double(r.x), double(r.y), double(r.z)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (s != one) {
|
if (s != one) {
|
||||||
|
@ -2601,8 +2600,7 @@ void FBXExporter::WriteModelNodes(
|
||||||
transform_chain.emplace_back(elem->first, t);
|
transform_chain.emplace_back(elem->first, t);
|
||||||
break;
|
break;
|
||||||
case 'r': // rotation
|
case 'r': // rotation
|
||||||
r *= float(DEG);
|
transform_chain.emplace_back(elem->first, AI_RAD_TO_DEG(r));
|
||||||
transform_chain.emplace_back(elem->first, r);
|
|
||||||
break;
|
break;
|
||||||
case 's': // scale
|
case 's': // scale
|
||||||
transform_chain.emplace_back(elem->first, s);
|
transform_chain.emplace_back(elem->first, s);
|
||||||
|
|
|
@ -38,9 +38,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @file IFCBoolean.cpp
|
/// @file IFCBoolean.cpp
|
||||||
* @brief Implements a subset of Ifc boolean operations
|
/// @brief Implements a subset of Ifc boolean operations
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||||
|
|
||||||
|
@ -48,7 +47,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
#include "Common/PolyTools.h"
|
#include "Common/PolyTools.h"
|
||||||
#include "PostProcessing/ProcessHelper.h"
|
#include "PostProcessing/ProcessHelper.h"
|
||||||
|
|
||||||
|
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
@ -67,8 +65,9 @@ bool IntersectSegmentPlane(const IfcVector3 &p, const IfcVector3 &n, const IfcVe
|
||||||
|
|
||||||
// if segment ends on plane, do not report a hit. We stay on that side until a following segment starting at this
|
// if segment ends on plane, do not report a hit. We stay on that side until a following segment starting at this
|
||||||
// point leaves the plane through the other side
|
// point leaves the plane through the other side
|
||||||
if (std::abs(dotOne + dotTwo) < ai_epsilon)
|
if (std::abs(dotOne + dotTwo) < ai_epsilon) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// if segment starts on the plane, report a hit only if the end lies on the *other* side
|
// if segment starts on the plane, report a hit only if the end lies on the *other* side
|
||||||
if (std::abs(dotTwo) < ai_epsilon) {
|
if (std::abs(dotTwo) < ai_epsilon) {
|
||||||
|
@ -82,13 +81,15 @@ bool IntersectSegmentPlane(const IfcVector3 &p, const IfcVector3 &n, const IfcVe
|
||||||
|
|
||||||
// ignore if segment is parallel to plane and far away from it on either side
|
// ignore if segment is parallel to plane and far away from it on either side
|
||||||
// Warning: if there's a few thousand of such segments which slowly accumulate beyond the epsilon, no hit would be registered
|
// Warning: if there's a few thousand of such segments which slowly accumulate beyond the epsilon, no hit would be registered
|
||||||
if (std::abs(dotOne) < ai_epsilon)
|
if (std::abs(dotOne) < ai_epsilon) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// t must be in [0..1] if the intersection point is within the given segment
|
// t must be in [0..1] if the intersection point is within the given segment
|
||||||
const IfcFloat t = dotTwo / dotOne;
|
const IfcFloat t = dotTwo / dotOne;
|
||||||
if (t > 1.0 || t < 0.0)
|
if (t > 1.0 || t < 0.0) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
out = e0 + t * seg;
|
out = e0 + t * seg;
|
||||||
return true;
|
return true;
|
||||||
|
@ -110,12 +111,14 @@ void FilterPolygon(std::vector<IfcVector3> &resultpoly) {
|
||||||
FuzzyVectorCompare fz(epsilon);
|
FuzzyVectorCompare fz(epsilon);
|
||||||
std::vector<IfcVector3>::iterator e = std::unique(resultpoly.begin(), resultpoly.end(), fz);
|
std::vector<IfcVector3>::iterator e = std::unique(resultpoly.begin(), resultpoly.end(), fz);
|
||||||
|
|
||||||
if (e != resultpoly.end())
|
if (e != resultpoly.end()) {
|
||||||
resultpoly.erase(e, resultpoly.end());
|
resultpoly.erase(e, resultpoly.end());
|
||||||
|
}
|
||||||
|
|
||||||
if (!resultpoly.empty() && fz(resultpoly.front(), resultpoly.back()))
|
if (!resultpoly.empty() && fz(resultpoly.front(), resultpoly.back())) {
|
||||||
resultpoly.pop_back();
|
resultpoly.pop_back();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void WritePolygon(std::vector<IfcVector3> &resultpoly, TempMesh &result) {
|
void WritePolygon(std::vector<IfcVector3> &resultpoly, TempMesh &result) {
|
||||||
|
@ -291,8 +294,9 @@ bool IntersectsBoundaryProfile(const IfcVector3 &e0, const IfcVector3 &e1, const
|
||||||
}
|
}
|
||||||
|
|
||||||
// Line segment ends at boundary -> ignore any hit, it will be handled by possibly following segments
|
// Line segment ends at boundary -> ignore any hit, it will be handled by possibly following segments
|
||||||
if (endsAtSegment && !halfOpen)
|
if (endsAtSegment && !halfOpen) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Line segment starts at boundary -> generate a hit only if following that line would change the INSIDE/OUTSIDE
|
// Line segment starts at boundary -> generate a hit only if following that line would change the INSIDE/OUTSIDE
|
||||||
// state. This should catch the case where a connected set of segments has a point directly on the boundary,
|
// state. This should catch the case where a connected set of segments has a point directly on the boundary,
|
||||||
|
@ -301,16 +305,18 @@ bool IntersectsBoundaryProfile(const IfcVector3 &e0, const IfcVector3 &e1, const
|
||||||
if (startsAtSegment) {
|
if (startsAtSegment) {
|
||||||
IfcVector3 inside_dir = IfcVector3(b.y, -b.x, 0.0) * windingOrder;
|
IfcVector3 inside_dir = IfcVector3(b.y, -b.x, 0.0) * windingOrder;
|
||||||
bool isGoingInside = (inside_dir * e) > 0.0;
|
bool isGoingInside = (inside_dir * e) > 0.0;
|
||||||
if (isGoingInside == isStartAssumedInside)
|
if (isGoingInside == isStartAssumedInside) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// only insert the point into the list if it is sufficiently far away from the previous intersection point.
|
// only insert the point into the list if it is sufficiently far away from the previous intersection point.
|
||||||
// This way, we avoid duplicate detection if the intersection is directly on the vertex between two segments.
|
// This way, we avoid duplicate detection if the intersection is directly on the vertex between two segments.
|
||||||
if (!intersect_results.empty() && intersect_results.back().first == i - 1) {
|
if (!intersect_results.empty() && intersect_results.back().first == i - 1) {
|
||||||
const IfcVector3 diff = intersect_results.back().second - e0;
|
const IfcVector3 diff = intersect_results.back().second - e0;
|
||||||
if (IfcVector2(diff.x, diff.y).SquareLength() < 1e-10)
|
if (IfcVector2(diff.x, diff.y).SquareLength() < 1e-10) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
intersect_results.emplace_back(i, e0);
|
intersect_results.emplace_back(i, e0);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -322,9 +328,10 @@ bool IntersectsBoundaryProfile(const IfcVector3 &e0, const IfcVector3 &e1, const
|
||||||
// This way, we avoid duplicate detection if the intersection is directly on the vertex between two segments.
|
// This way, we avoid duplicate detection if the intersection is directly on the vertex between two segments.
|
||||||
if (!intersect_results.empty() && intersect_results.back().first == i - 1) {
|
if (!intersect_results.empty() && intersect_results.back().first == i - 1) {
|
||||||
const IfcVector3 diff = intersect_results.back().second - p;
|
const IfcVector3 diff = intersect_results.back().second - p;
|
||||||
if (IfcVector2(diff.x, diff.y).SquareLength() < 1e-10)
|
if (IfcVector2(diff.x, diff.y).SquareLength() < 1e-10) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
intersect_results.emplace_back(i, p);
|
intersect_results.emplace_back(i, p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -662,7 +669,8 @@ void ProcessPolygonalBoundedBooleanHalfSpaceDifference(const Schema_2x3::IfcPoly
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedAreaSolid *as, TempMesh &result,
|
void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedAreaSolid *as,
|
||||||
|
TempMesh &result,
|
||||||
const TempMesh &first_operand,
|
const TempMesh &first_operand,
|
||||||
ConversionData &conv) {
|
ConversionData &conv) {
|
||||||
ai_assert(as != nullptr);
|
ai_assert(as != nullptr);
|
||||||
|
@ -763,4 +771,4 @@ void ProcessBoolean(const Schema_2x3::IfcBooleanResult &boolean, TempMesh &resul
|
||||||
} // namespace IFC
|
} // namespace IFC
|
||||||
} // namespace Assimp
|
} // namespace Assimp
|
||||||
|
|
||||||
#endif
|
#endif // ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||||
|
|
|
@ -39,15 +39,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @file IFCProfile.cpp
|
/// @file IFCProfile.cpp
|
||||||
* @brief Read profile and curves entities from IFC files
|
/// @brief Read profile and curves entities from IFC files
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||||
#include "IFCUtil.h"
|
#include "IFCUtil.h"
|
||||||
|
|
||||||
namespace Assimp {
|
namespace Assimp {
|
||||||
namespace IFC {
|
namespace IFC {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------
|
||||||
|
@ -56,8 +56,7 @@ namespace {
|
||||||
class Conic : public Curve {
|
class Conic : public Curve {
|
||||||
public:
|
public:
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
Conic(const Schema_2x3::IfcConic& entity, ConversionData& conv)
|
Conic(const Schema_2x3::IfcConic& entity, ConversionData& conv) : Curve(entity,conv) {
|
||||||
: Curve(entity,conv) {
|
|
||||||
IfcMatrix4 trafo;
|
IfcMatrix4 trafo;
|
||||||
ConvertAxisPlacement(trafo,*entity.Position,conv);
|
ConvertAxisPlacement(trafo,*entity.Position,conv);
|
||||||
|
|
||||||
|
@ -69,12 +68,12 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
bool IsClosed() const {
|
bool IsClosed() const override {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
|
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const override {
|
||||||
ai_assert( InRange( a ) );
|
ai_assert( InRange( a ) );
|
||||||
ai_assert( InRange( b ) );
|
ai_assert( InRange( b ) );
|
||||||
|
|
||||||
|
@ -88,7 +87,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
ParamRange GetParametricRange() const {
|
ParamRange GetParametricRange() const override {
|
||||||
return std::make_pair(static_cast<IfcFloat>( 0. ), static_cast<IfcFloat>( AI_MATH_TWO_PI / conv.angle_scale ));
|
return std::make_pair(static_cast<IfcFloat>( 0. ), static_cast<IfcFloat>( AI_MATH_TWO_PI / conv.angle_scale ));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,14 +101,13 @@ protected:
|
||||||
class Circle : public Conic {
|
class Circle : public Conic {
|
||||||
public:
|
public:
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
Circle(const Schema_2x3::IfcCircle& entity, ConversionData& conv)
|
Circle(const Schema_2x3::IfcCircle& entity, ConversionData& conv) : Conic(entity,conv) , entity(entity) {}
|
||||||
: Conic(entity,conv)
|
|
||||||
, entity(entity)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
IfcVector3 Eval(IfcFloat u) const {
|
~Circle() override = default;
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
IfcVector3 Eval(IfcFloat u) const override {
|
||||||
u = -conv.angle_scale * u;
|
u = -conv.angle_scale * u;
|
||||||
return location + static_cast<IfcFloat>(entity.Radius)*(static_cast<IfcFloat>(std::cos(u))*p[0] +
|
return location + static_cast<IfcFloat>(entity.Radius)*(static_cast<IfcFloat>(std::cos(u))*p[0] +
|
||||||
static_cast<IfcFloat>(std::sin(u))*p[1]);
|
static_cast<IfcFloat>(std::sin(u))*p[1]);
|
||||||
|
@ -132,7 +130,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
IfcVector3 Eval(IfcFloat u) const {
|
IfcVector3 Eval(IfcFloat u) const override {
|
||||||
u = -conv.angle_scale * u;
|
u = -conv.angle_scale * u;
|
||||||
return location + static_cast<IfcFloat>(entity.SemiAxis1)*static_cast<IfcFloat>(std::cos(u))*p[0] +
|
return location + static_cast<IfcFloat>(entity.SemiAxis1)*static_cast<IfcFloat>(std::cos(u))*p[0] +
|
||||||
static_cast<IfcFloat>(entity.SemiAxis2)*static_cast<IfcFloat>(std::sin(u))*p[1];
|
static_cast<IfcFloat>(entity.SemiAxis2)*static_cast<IfcFloat>(std::sin(u))*p[1];
|
||||||
|
@ -155,17 +153,17 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
bool IsClosed() const {
|
bool IsClosed() const override {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
IfcVector3 Eval(IfcFloat u) const {
|
IfcVector3 Eval(IfcFloat u) const override {
|
||||||
return p + u*v;
|
return p + u*v;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
|
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const override {
|
||||||
ai_assert( InRange( a ) );
|
ai_assert( InRange( a ) );
|
||||||
ai_assert( InRange( b ) );
|
ai_assert( InRange( b ) );
|
||||||
// two points are always sufficient for a line segment
|
// two points are always sufficient for a line segment
|
||||||
|
@ -174,7 +172,7 @@ public:
|
||||||
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const {
|
void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const override {
|
||||||
ai_assert( InRange( a ) );
|
ai_assert( InRange( a ) );
|
||||||
ai_assert( InRange( b ) );
|
ai_assert( InRange( b ) );
|
||||||
|
|
||||||
|
@ -188,7 +186,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
ParamRange GetParametricRange() const {
|
ParamRange GetParametricRange() const override {
|
||||||
const IfcFloat inf = std::numeric_limits<IfcFloat>::infinity();
|
const IfcFloat inf = std::numeric_limits<IfcFloat>::infinity();
|
||||||
|
|
||||||
return std::make_pair(-inf,+inf);
|
return std::make_pair(-inf,+inf);
|
||||||
|
@ -234,7 +232,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
IfcVector3 Eval(IfcFloat u) const {
|
IfcVector3 Eval(IfcFloat u) const override {
|
||||||
if (curves.empty()) {
|
if (curves.empty()) {
|
||||||
return IfcVector3();
|
return IfcVector3();
|
||||||
}
|
}
|
||||||
|
@ -254,7 +252,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
|
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const override {
|
||||||
ai_assert( InRange( a ) );
|
ai_assert( InRange( a ) );
|
||||||
ai_assert( InRange( b ) );
|
ai_assert( InRange( b ) );
|
||||||
size_t cnt = 0;
|
size_t cnt = 0;
|
||||||
|
@ -275,7 +273,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const {
|
void SampleDiscrete(TempMesh& out,IfcFloat a, IfcFloat b) const override {
|
||||||
ai_assert( InRange( a ) );
|
ai_assert( InRange( a ) );
|
||||||
ai_assert( InRange( b ) );
|
ai_assert( InRange( b ) );
|
||||||
|
|
||||||
|
@ -293,7 +291,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
ParamRange GetParametricRange() const {
|
ParamRange GetParametricRange() const override {
|
||||||
return std::make_pair(static_cast<IfcFloat>( 0. ),total);
|
return std::make_pair(static_cast<IfcFloat>( 0. ),total);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,27 +371,27 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
IfcVector3 Eval(IfcFloat p) const {
|
IfcVector3 Eval(IfcFloat p) const override {
|
||||||
ai_assert(InRange(p));
|
ai_assert(InRange(p));
|
||||||
return base->Eval( TrimParam(p) );
|
return base->Eval( TrimParam(p) );
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
|
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const override {
|
||||||
ai_assert( InRange( a ) );
|
ai_assert( InRange( a ) );
|
||||||
ai_assert( InRange( b ) );
|
ai_assert( InRange( b ) );
|
||||||
return base->EstimateSampleCount(TrimParam(a),TrimParam(b));
|
return base->EstimateSampleCount(TrimParam(a),TrimParam(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
void SampleDiscrete(TempMesh& out,IfcFloat a,IfcFloat b) const {
|
void SampleDiscrete(TempMesh& out,IfcFloat a,IfcFloat b) const override {
|
||||||
ai_assert(InRange(a));
|
ai_assert(InRange(a));
|
||||||
ai_assert(InRange(b));
|
ai_assert(InRange(b));
|
||||||
return base->SampleDiscrete(out,TrimParam(a),TrimParam(b));
|
return base->SampleDiscrete(out,TrimParam(a),TrimParam(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
ParamRange GetParametricRange() const {
|
ParamRange GetParametricRange() const override {
|
||||||
return std::make_pair(static_cast<IfcFloat>( 0. ),maxval);
|
return std::make_pair(static_cast<IfcFloat>( 0. ),maxval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,7 +429,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
IfcVector3 Eval(IfcFloat p) const {
|
IfcVector3 Eval(IfcFloat p) const override {
|
||||||
ai_assert(InRange(p));
|
ai_assert(InRange(p));
|
||||||
|
|
||||||
const size_t b = static_cast<size_t>(std::floor(p));
|
const size_t b = static_cast<size_t>(std::floor(p));
|
||||||
|
@ -444,14 +442,14 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const {
|
size_t EstimateSampleCount(IfcFloat a, IfcFloat b) const override {
|
||||||
ai_assert(InRange(a));
|
ai_assert(InRange(a));
|
||||||
ai_assert(InRange(b));
|
ai_assert(InRange(b));
|
||||||
return static_cast<size_t>( std::ceil(b) - std::floor(a) );
|
return static_cast<size_t>( std::ceil(b) - std::floor(a) );
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
ParamRange GetParametricRange() const {
|
ParamRange GetParametricRange() const override {
|
||||||
return std::make_pair(static_cast<IfcFloat>( 0. ),static_cast<IfcFloat>(points.size()-1));
|
return std::make_pair(static_cast<IfcFloat>( 0. ),static_cast<IfcFloat>(points.size()-1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -516,7 +514,7 @@ size_t Curve::EstimateSampleCount(IfcFloat a, IfcFloat b) const {
|
||||||
ai_assert( InRange( a ) );
|
ai_assert( InRange( a ) );
|
||||||
ai_assert( InRange( b ) );
|
ai_assert( InRange( b ) );
|
||||||
|
|
||||||
// arbitrary default value, deriving classes should supply better suited values
|
// arbitrary default value, deriving classes should supply better-suited values
|
||||||
return 16;
|
return 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,24 +38,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @file IFCGeometry.cpp
|
/// @file IFCGeometry.cpp
|
||||||
* @brief Geometry conversion and synthesis for IFC
|
/// @brief Geometry conversion and synthesis for IFC
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||||
#include "IFCUtil.h"
|
#include "IFCUtil.h"
|
||||||
#include "Common/PolyTools.h"
|
#include "Common/PolyTools.h"
|
||||||
#include "PostProcessing/ProcessHelper.h"
|
#include "PostProcessing/ProcessHelper.h"
|
||||||
|
#include "contrib/poly2tri/poly2tri/poly2tri.h"
|
||||||
#ifdef ASSIMP_USE_HUNTER
|
#include "contrib/clipper/clipper.hpp"
|
||||||
# include <poly2tri/poly2tri.h>
|
|
||||||
# include <polyclipping/clipper.hpp>
|
|
||||||
#else
|
|
||||||
# include "../contrib/poly2tri/poly2tri/poly2tri.h"
|
|
||||||
# include "../contrib/clipper/clipper.hpp"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -65,8 +56,7 @@ namespace Assimp {
|
||||||
namespace IFC {
|
namespace IFC {
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
bool ProcessPolyloop(const Schema_2x3::IfcPolyLoop& loop, TempMesh& meshout, ConversionData& /*conv*/)
|
bool ProcessPolyloop(const Schema_2x3::IfcPolyLoop& loop, TempMesh& meshout, ConversionData& /*conv*/) {
|
||||||
{
|
|
||||||
size_t cnt = 0;
|
size_t cnt = 0;
|
||||||
for(const Schema_2x3::IfcCartesianPoint& c : loop.Polygon) {
|
for(const Schema_2x3::IfcCartesianPoint& c : loop.Polygon) {
|
||||||
IfcVector3 tmp;
|
IfcVector3 tmp;
|
||||||
|
@ -91,8 +81,7 @@ bool ProcessPolyloop(const Schema_2x3::IfcPolyLoop& loop, TempMesh& meshout, Con
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t master_bounds = (size_t)-1)
|
void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t master_bounds = (size_t)-1) {
|
||||||
{
|
|
||||||
// handle all trivial cases
|
// handle all trivial cases
|
||||||
if(inmesh.mVertcnt.empty()) {
|
if(inmesh.mVertcnt.empty()) {
|
||||||
return;
|
return;
|
||||||
|
@ -127,8 +116,7 @@ void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t m
|
||||||
if (master_bounds != (size_t)-1) {
|
if (master_bounds != (size_t)-1) {
|
||||||
ai_assert(master_bounds < inmesh.mVertcnt.size());
|
ai_assert(master_bounds < inmesh.mVertcnt.size());
|
||||||
outer_polygon_it = begin + master_bounds;
|
outer_polygon_it = begin + master_bounds;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
for(iit = begin; iit != end; ++iit) {
|
for(iit = begin; iit != end; ++iit) {
|
||||||
// find the polygon with the largest area and take it as the outer bound.
|
// find the polygon with the largest area and take it as the outer bound.
|
||||||
IfcVector3& n = normals[std::distance(begin,iit)];
|
IfcVector3& n = normals[std::distance(begin,iit)];
|
||||||
|
@ -139,6 +127,7 @@ void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t m
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outer_polygon_it == end) {
|
if (outer_polygon_it == end) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -205,40 +194,20 @@ void ProcessConnectedFaceSet(const Schema_2x3::IfcConnectedFaceSet& fset, TempMe
|
||||||
|
|
||||||
if(const Schema_2x3::IfcPolyLoop* const polyloop = bound.Bound->ToPtr<Schema_2x3::IfcPolyLoop>()) {
|
if(const Schema_2x3::IfcPolyLoop* const polyloop = bound.Bound->ToPtr<Schema_2x3::IfcPolyLoop>()) {
|
||||||
if(ProcessPolyloop(*polyloop, meshout,conv)) {
|
if(ProcessPolyloop(*polyloop, meshout,conv)) {
|
||||||
|
|
||||||
// The outer boundary is better determined by checking which
|
// The outer boundary is better determined by checking which
|
||||||
// polygon covers the largest area.
|
// polygon covers the largest area.
|
||||||
|
|
||||||
//if(bound.ToPtr<IfcFaceOuterBound>()) {
|
|
||||||
// ob = cnt;
|
|
||||||
//}
|
|
||||||
//++cnt;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
IFCImporter::LogWarn("skipping unknown IfcFaceBound entity, type is ", bound.Bound->GetClassName());
|
IFCImporter::LogWarn("skipping unknown IfcFaceBound entity, type is ", bound.Bound->GetClassName());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// And this, even though it is sometimes TRUE and sometimes FALSE,
|
|
||||||
// does not really improve results.
|
|
||||||
|
|
||||||
/*if(!IsTrue(bound.Orientation)) {
|
|
||||||
size_t c = 0;
|
|
||||||
for(unsigned int& c : meshout.vertcnt) {
|
|
||||||
std::reverse(result.verts.begin() + cnt,result.verts.begin() + cnt + c);
|
|
||||||
cnt += c;
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
ProcessPolygonBoundaries(result, meshout);
|
ProcessPolygonBoundaries(result, meshout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ProcessRevolvedAreaSolid(const Schema_2x3::IfcRevolvedAreaSolid& solid, TempMesh& result, ConversionData& conv)
|
void ProcessRevolvedAreaSolid(const Schema_2x3::IfcRevolvedAreaSolid& solid, TempMesh& result, ConversionData& conv) {
|
||||||
{
|
|
||||||
TempMesh meshout;
|
TempMesh meshout;
|
||||||
|
|
||||||
// first read the profile description
|
// first read the profile description
|
||||||
|
@ -265,7 +234,8 @@ void ProcessRevolvedAreaSolid(const Schema_2x3::IfcRevolvedAreaSolid& solid, Tem
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsigned int cnt_segments = std::max(2u,static_cast<unsigned int>(conv.settings.cylindricalTessellation * std::fabs(max_angle)/AI_MATH_HALF_PI_F));
|
const unsigned int cnt_segments =
|
||||||
|
std::max(2u,static_cast<unsigned int>(conv.settings.cylindricalTessellation * std::fabs(max_angle)/AI_MATH_HALF_PI_F));
|
||||||
const IfcFloat delta = max_angle/cnt_segments;
|
const IfcFloat delta = max_angle/cnt_segments;
|
||||||
|
|
||||||
has_area = has_area && std::fabs(max_angle) < AI_MATH_TWO_PI_F*0.99;
|
has_area = has_area && std::fabs(max_angle) < AI_MATH_TWO_PI_F*0.99;
|
||||||
|
@ -324,8 +294,9 @@ void ProcessRevolvedAreaSolid(const Schema_2x3::IfcRevolvedAreaSolid& solid, Tem
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ProcessSweptDiskSolid(const Schema_2x3::IfcSweptDiskSolid &solid, TempMesh& result, ConversionData& conv)
|
void ProcessSweptDiskSolid(const Schema_2x3::IfcSweptDiskSolid &solid,
|
||||||
{
|
TempMesh& result,
|
||||||
|
ConversionData& conv) {
|
||||||
const Curve* const curve = Curve::Convert(*solid.Directrix, conv);
|
const Curve* const curve = Curve::Convert(*solid.Directrix, conv);
|
||||||
if(!curve) {
|
if(!curve) {
|
||||||
IFCImporter::LogError("failed to convert Directrix curve (IfcSweptDiskSolid)");
|
IFCImporter::LogError("failed to convert Directrix curve (IfcSweptDiskSolid)");
|
||||||
|
@ -460,8 +431,7 @@ void ProcessSweptDiskSolid(const Schema_2x3::IfcSweptDiskSolid &solid, TempMesh&
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVector3& norOut)
|
IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVector3& norOut) {
|
||||||
{
|
|
||||||
const std::vector<IfcVector3>& out = curmesh.mVerts;
|
const std::vector<IfcVector3>& out = curmesh.mVerts;
|
||||||
IfcMatrix3 m;
|
IfcMatrix3 m;
|
||||||
|
|
||||||
|
@ -504,10 +474,6 @@ IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVect
|
||||||
IfcVector3 r = (out[idx]-any_point);
|
IfcVector3 r = (out[idx]-any_point);
|
||||||
r.Normalize();
|
r.Normalize();
|
||||||
|
|
||||||
//if(d) {
|
|
||||||
// *d = -any_point * nor;
|
|
||||||
//}
|
|
||||||
|
|
||||||
// Reconstruct orthonormal basis
|
// Reconstruct orthonormal basis
|
||||||
// XXX use Gram Schmidt for increased robustness
|
// XXX use Gram Schmidt for increased robustness
|
||||||
IfcVector3 u = r ^ nor;
|
IfcVector3 u = r ^ nor;
|
||||||
|
@ -531,8 +497,7 @@ IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVect
|
||||||
const auto closeDistance = ai_epsilon;
|
const auto closeDistance = ai_epsilon;
|
||||||
|
|
||||||
bool areClose(Schema_2x3::IfcCartesianPoint pt1,Schema_2x3::IfcCartesianPoint pt2) {
|
bool areClose(Schema_2x3::IfcCartesianPoint pt1,Schema_2x3::IfcCartesianPoint pt2) {
|
||||||
if(pt1.Coordinates.size() != pt2.Coordinates.size())
|
if(pt1.Coordinates.size() != pt2.Coordinates.size()) {
|
||||||
{
|
|
||||||
IFCImporter::LogWarn("unable to compare differently-dimensioned points");
|
IFCImporter::LogWarn("unable to compare differently-dimensioned points");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -540,11 +505,11 @@ bool areClose(Schema_2x3::IfcCartesianPoint pt1,Schema_2x3::IfcCartesianPoint pt
|
||||||
auto coord2 = pt2.Coordinates.begin();
|
auto coord2 = pt2.Coordinates.begin();
|
||||||
// we're just testing each dimension separately rather than doing euclidean distance, as we're
|
// we're just testing each dimension separately rather than doing euclidean distance, as we're
|
||||||
// looking for very close coordinates
|
// looking for very close coordinates
|
||||||
for(; coord1 != pt1.Coordinates.end(); coord1++,coord2++)
|
for(; coord1 != pt1.Coordinates.end(); coord1++,coord2++) {
|
||||||
{
|
if(std::fabs(*coord1 - *coord2) > closeDistance) {
|
||||||
if(std::fabs(*coord1 - *coord2) > closeDistance)
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -553,6 +518,7 @@ bool areClose(IfcVector3 pt1,IfcVector3 pt2) {
|
||||||
std::fabs(pt1.y - pt2.y) < closeDistance &&
|
std::fabs(pt1.y - pt2.y) < closeDistance &&
|
||||||
std::fabs(pt1.z - pt2.z) < closeDistance);
|
std::fabs(pt1.z - pt2.z) < closeDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extrudes the given polygon along the direction, converts it into an opening or applies all openings as necessary.
|
// Extrudes the given polygon along the direction, converts it into an opening or applies all openings as necessary.
|
||||||
void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const TempMesh& curve,
|
void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const TempMesh& curve,
|
||||||
const IfcVector3& extrusionDir, TempMesh& result, ConversionData &conv, bool collect_openings)
|
const IfcVector3& extrusionDir, TempMesh& result, ConversionData &conv, bool collect_openings)
|
||||||
|
@ -590,8 +556,9 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te
|
||||||
|
|
||||||
// reverse profile polygon if it's winded in the wrong direction in relation to the extrusion direction
|
// reverse profile polygon if it's winded in the wrong direction in relation to the extrusion direction
|
||||||
IfcVector3 profileNormal = TempMesh::ComputePolygonNormal(in.data(), in.size());
|
IfcVector3 profileNormal = TempMesh::ComputePolygonNormal(in.data(), in.size());
|
||||||
if( profileNormal * dir < 0.0 )
|
if( profileNormal * dir < 0.0 ) {
|
||||||
std::reverse(in.begin(), in.end());
|
std::reverse(in.begin(), in.end());
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<IfcVector3> nors;
|
std::vector<IfcVector3> nors;
|
||||||
const bool openings = !!conv.apply_openings && conv.apply_openings->size();
|
const bool openings = !!conv.apply_openings && conv.apply_openings->size();
|
||||||
|
@ -678,8 +645,7 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te
|
||||||
if(n > 0) {
|
if(n > 0) {
|
||||||
for(size_t i = 0; i < in.size(); ++i)
|
for(size_t i = 0; i < in.size(); ++i)
|
||||||
out.push_back(in[i] + dir);
|
out.push_back(in[i] + dir);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
for(size_t i = in.size(); i--; )
|
for(size_t i = in.size(); i--; )
|
||||||
out.push_back(in[i]);
|
out.push_back(in[i]);
|
||||||
}
|
}
|
||||||
|
@ -721,9 +687,10 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ProcessExtrudedAreaSolid(const Schema_2x3::IfcExtrudedAreaSolid& solid, TempMesh& result,
|
void ProcessExtrudedAreaSolid(const Schema_2x3::IfcExtrudedAreaSolid& solid,
|
||||||
ConversionData& conv, bool collect_openings)
|
TempMesh& result,
|
||||||
{
|
ConversionData& conv,
|
||||||
|
bool collect_openings) {
|
||||||
TempMesh meshout;
|
TempMesh meshout;
|
||||||
|
|
||||||
// First read the profile description.
|
// First read the profile description.
|
||||||
|
@ -761,24 +728,23 @@ void ProcessExtrudedAreaSolid(const Schema_2x3::IfcExtrudedAreaSolid& solid, Tem
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ProcessSweptAreaSolid(const Schema_2x3::IfcSweptAreaSolid& swept, TempMesh& meshout,
|
void ProcessSweptAreaSolid(const Schema_2x3::IfcSweptAreaSolid& swept,
|
||||||
ConversionData& conv)
|
TempMesh& meshout,
|
||||||
{
|
ConversionData& conv) {
|
||||||
if(const Schema_2x3::IfcExtrudedAreaSolid* const solid = swept.ToPtr<Schema_2x3::IfcExtrudedAreaSolid>()) {
|
if(const Schema_2x3::IfcExtrudedAreaSolid* const solid = swept.ToPtr<Schema_2x3::IfcExtrudedAreaSolid>()) {
|
||||||
ProcessExtrudedAreaSolid(*solid,meshout,conv, !!conv.collect_openings);
|
ProcessExtrudedAreaSolid(*solid,meshout,conv, !!conv.collect_openings);
|
||||||
}
|
} else if(const Schema_2x3::IfcRevolvedAreaSolid* const rev = swept.ToPtr<Schema_2x3::IfcRevolvedAreaSolid>()) {
|
||||||
else if(const Schema_2x3::IfcRevolvedAreaSolid* const rev = swept.ToPtr<Schema_2x3::IfcRevolvedAreaSolid>()) {
|
|
||||||
ProcessRevolvedAreaSolid(*rev,meshout,conv);
|
ProcessRevolvedAreaSolid(*rev,meshout,conv);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
IFCImporter::LogWarn("skipping unknown IfcSweptAreaSolid entity, type is ", swept.GetClassName());
|
IFCImporter::LogWarn("skipping unknown IfcSweptAreaSolid entity, type is ", swept.GetClassName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo, unsigned int matid, std::set<unsigned int>& mesh_indices,
|
bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo,
|
||||||
ConversionData& conv)
|
unsigned int matid,
|
||||||
{
|
std::set<unsigned int>& mesh_indices,
|
||||||
|
ConversionData& conv) {
|
||||||
bool fix_orientation = false;
|
bool fix_orientation = false;
|
||||||
std::shared_ptr< TempMesh > meshtmp = std::make_shared<TempMesh>();
|
std::shared_ptr< TempMesh > meshtmp = std::make_shared<TempMesh>();
|
||||||
if(const Schema_2x3::IfcShellBasedSurfaceModel* shellmod = geo.ToPtr<Schema_2x3::IfcShellBasedSurfaceModel>()) {
|
if(const Schema_2x3::IfcShellBasedSurfaceModel* shellmod = geo.ToPtr<Schema_2x3::IfcShellBasedSurfaceModel>()) {
|
||||||
|
@ -788,41 +754,32 @@ bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo, unsigned
|
||||||
const Schema_2x3::IfcConnectedFaceSet& fs = conv.db.MustGetObject(e).To<Schema_2x3::IfcConnectedFaceSet>();
|
const Schema_2x3::IfcConnectedFaceSet& fs = conv.db.MustGetObject(e).To<Schema_2x3::IfcConnectedFaceSet>();
|
||||||
|
|
||||||
ProcessConnectedFaceSet(fs, *meshtmp, conv);
|
ProcessConnectedFaceSet(fs, *meshtmp, conv);
|
||||||
}
|
} catch(std::bad_cast&) {
|
||||||
catch(std::bad_cast&) {
|
|
||||||
IFCImporter::LogWarn("unexpected type error, IfcShell ought to inherit from IfcConnectedFaceSet");
|
IFCImporter::LogWarn("unexpected type error, IfcShell ought to inherit from IfcConnectedFaceSet");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fix_orientation = true;
|
fix_orientation = true;
|
||||||
}
|
} else if(const Schema_2x3::IfcConnectedFaceSet* fset = geo.ToPtr<Schema_2x3::IfcConnectedFaceSet>()) {
|
||||||
else if(const Schema_2x3::IfcConnectedFaceSet* fset = geo.ToPtr<Schema_2x3::IfcConnectedFaceSet>()) {
|
|
||||||
ProcessConnectedFaceSet(*fset, *meshtmp, conv);
|
ProcessConnectedFaceSet(*fset, *meshtmp, conv);
|
||||||
fix_orientation = true;
|
fix_orientation = true;
|
||||||
}
|
} else if(const Schema_2x3::IfcSweptAreaSolid* swept = geo.ToPtr<Schema_2x3::IfcSweptAreaSolid>()) {
|
||||||
else if(const Schema_2x3::IfcSweptAreaSolid* swept = geo.ToPtr<Schema_2x3::IfcSweptAreaSolid>()) {
|
|
||||||
ProcessSweptAreaSolid(*swept, *meshtmp, conv);
|
ProcessSweptAreaSolid(*swept, *meshtmp, conv);
|
||||||
}
|
} else if(const Schema_2x3::IfcSweptDiskSolid* disk = geo.ToPtr<Schema_2x3::IfcSweptDiskSolid>()) {
|
||||||
else if(const Schema_2x3::IfcSweptDiskSolid* disk = geo.ToPtr<Schema_2x3::IfcSweptDiskSolid>()) {
|
|
||||||
ProcessSweptDiskSolid(*disk, *meshtmp, conv);
|
ProcessSweptDiskSolid(*disk, *meshtmp, conv);
|
||||||
}
|
} else if(const Schema_2x3::IfcManifoldSolidBrep* brep = geo.ToPtr<Schema_2x3::IfcManifoldSolidBrep>()) {
|
||||||
else if(const Schema_2x3::IfcManifoldSolidBrep* brep = geo.ToPtr<Schema_2x3::IfcManifoldSolidBrep>()) {
|
|
||||||
ProcessConnectedFaceSet(brep->Outer, *meshtmp, conv);
|
ProcessConnectedFaceSet(brep->Outer, *meshtmp, conv);
|
||||||
fix_orientation = true;
|
fix_orientation = true;
|
||||||
}
|
} else if(const Schema_2x3::IfcFaceBasedSurfaceModel* surf = geo.ToPtr<Schema_2x3::IfcFaceBasedSurfaceModel>()) {
|
||||||
else if(const Schema_2x3::IfcFaceBasedSurfaceModel* surf = geo.ToPtr<Schema_2x3::IfcFaceBasedSurfaceModel>()) {
|
|
||||||
for(const Schema_2x3::IfcConnectedFaceSet& fc : surf->FbsmFaces) {
|
for(const Schema_2x3::IfcConnectedFaceSet& fc : surf->FbsmFaces) {
|
||||||
ProcessConnectedFaceSet(fc, *meshtmp, conv);
|
ProcessConnectedFaceSet(fc, *meshtmp, conv);
|
||||||
}
|
}
|
||||||
fix_orientation = true;
|
fix_orientation = true;
|
||||||
}
|
} else if(const Schema_2x3::IfcBooleanResult* boolean = geo.ToPtr<Schema_2x3::IfcBooleanResult>()) {
|
||||||
else if(const Schema_2x3::IfcBooleanResult* boolean = geo.ToPtr<Schema_2x3::IfcBooleanResult>()) {
|
|
||||||
ProcessBoolean(*boolean, *meshtmp, conv);
|
ProcessBoolean(*boolean, *meshtmp, conv);
|
||||||
}
|
} else if(geo.ToPtr<Schema_2x3::IfcBoundingBox>()) {
|
||||||
else if(geo.ToPtr<Schema_2x3::IfcBoundingBox>()) {
|
|
||||||
// silently skip over bounding boxes
|
// silently skip over bounding boxes
|
||||||
return false;
|
return false;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
std::stringstream toLog;
|
std::stringstream toLog;
|
||||||
toLog << "skipping unknown IfcGeometricRepresentationItem entity, type is " << geo.GetClassName() << " id is " << geo.GetID();
|
toLog << "skipping unknown IfcGeometricRepresentationItem entity, type is " << geo.GetClassName() << " id is " << geo.GetID();
|
||||||
IFCImporter::LogWarn(toLog.str().c_str());
|
IFCImporter::LogWarn(toLog.str().c_str());
|
||||||
|
@ -868,9 +825,7 @@ bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo, unsigned
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void AssignAddedMeshes(std::set<unsigned int>& mesh_indices,aiNode* nd,
|
void AssignAddedMeshes(std::set<unsigned int>& mesh_indices,aiNode* nd, ConversionData& /*conv*/) {
|
||||||
ConversionData& /*conv*/)
|
|
||||||
{
|
|
||||||
if (!mesh_indices.empty()) {
|
if (!mesh_indices.empty()) {
|
||||||
std::set<unsigned int>::const_iterator it = mesh_indices.cbegin();
|
std::set<unsigned int>::const_iterator it = mesh_indices.cbegin();
|
||||||
std::set<unsigned int>::const_iterator end = mesh_indices.cend();
|
std::set<unsigned int>::const_iterator end = mesh_indices.cend();
|
||||||
|
@ -886,9 +841,9 @@ void AssignAddedMeshes(std::set<unsigned int>& mesh_indices,aiNode* nd,
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
bool TryQueryMeshCache(const Schema_2x3::IfcRepresentationItem& item,
|
bool TryQueryMeshCache(const Schema_2x3::IfcRepresentationItem& item,
|
||||||
std::set<unsigned int>& mesh_indices, unsigned int mat_index,
|
std::set<unsigned int>& mesh_indices,
|
||||||
ConversionData& conv)
|
unsigned int mat_index,
|
||||||
{
|
ConversionData& conv) {
|
||||||
ConversionData::MeshCacheIndex idx(&item, mat_index);
|
ConversionData::MeshCacheIndex idx(&item, mat_index);
|
||||||
ConversionData::MeshCache::const_iterator it = conv.cached_meshes.find(idx);
|
ConversionData::MeshCache::const_iterator it = conv.cached_meshes.find(idx);
|
||||||
if (it != conv.cached_meshes.end()) {
|
if (it != conv.cached_meshes.end()) {
|
||||||
|
@ -900,18 +855,18 @@ bool TryQueryMeshCache(const Schema_2x3::IfcRepresentationItem& item,
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void PopulateMeshCache(const Schema_2x3::IfcRepresentationItem& item,
|
void PopulateMeshCache(const Schema_2x3::IfcRepresentationItem& item,
|
||||||
const std::set<unsigned int>& mesh_indices, unsigned int mat_index,
|
const std::set<unsigned int>& mesh_indices,
|
||||||
ConversionData& conv)
|
unsigned int mat_index,
|
||||||
{
|
ConversionData& conv) {
|
||||||
ConversionData::MeshCacheIndex idx(&item, mat_index);
|
ConversionData::MeshCacheIndex idx(&item, mat_index);
|
||||||
conv.cached_meshes[idx] = mesh_indices;
|
conv.cached_meshes[idx] = mesh_indices;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
bool ProcessRepresentationItem(const Schema_2x3::IfcRepresentationItem& item, unsigned int matid,
|
bool ProcessRepresentationItem(const Schema_2x3::IfcRepresentationItem& item,
|
||||||
|
unsigned int matid,
|
||||||
std::set<unsigned int>& mesh_indices,
|
std::set<unsigned int>& mesh_indices,
|
||||||
ConversionData& conv)
|
ConversionData& conv) {
|
||||||
{
|
|
||||||
// determine material
|
// determine material
|
||||||
unsigned int localmatid = ProcessMaterials(item.GetID(), matid, conv, true);
|
unsigned int localmatid = ProcessMaterials(item.GetID(), matid, conv, true);
|
||||||
|
|
||||||
|
@ -920,8 +875,9 @@ bool ProcessRepresentationItem(const Schema_2x3::IfcRepresentationItem& item, un
|
||||||
if(mesh_indices.size()) {
|
if(mesh_indices.size()) {
|
||||||
PopulateMeshCache(item,mesh_indices,localmatid,conv);
|
PopulateMeshCache(item,mesh_indices,localmatid,conv);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
else return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -930,4 +886,4 @@ bool ProcessRepresentationItem(const Schema_2x3::IfcRepresentationItem& item, un
|
||||||
} // ! IFC
|
} // ! IFC
|
||||||
} // ! Assimp
|
} // ! Assimp
|
||||||
|
|
||||||
#endif
|
#endif // ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||||
|
|
|
@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
|
||||||
|
|
||||||
Copyright (c) 2006-2022, assimp team
|
Copyright (c) 2006-2022, assimp team
|
||||||
|
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use of this software in source and binary forms,
|
Redistribution and use of this software in source and binary forms,
|
||||||
|
@ -40,9 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @file IFCLoad.cpp
|
/// @file IFCLoad.cpp
|
||||||
* @brief Implementation of the Industry Foundation Classes loader.
|
/// @brief Implementation of the Industry Foundation Classes loader.
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||||
|
|
||||||
|
@ -92,7 +90,6 @@ using namespace Assimp::IFC;
|
||||||
IfcUnitAssignment
|
IfcUnitAssignment
|
||||||
IfcClosedShell
|
IfcClosedShell
|
||||||
IfcDoor
|
IfcDoor
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -119,14 +116,6 @@ static const aiImporterDesc desc = {
|
||||||
"ifc ifczip step stp"
|
"ifc ifczip step stp"
|
||||||
};
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
|
||||||
// Constructor to be privately used by Importer
|
|
||||||
IFCImporter::IFCImporter() = default;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
|
||||||
// Destructor, private as well
|
|
||||||
IFCImporter::~IFCImporter() = default;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
// Returns whether the class can handle the format of the given file.
|
// Returns whether the class can handle the format of the given file.
|
||||||
bool IFCImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
|
bool IFCImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
|
||||||
|
@ -196,7 +185,7 @@ void IFCImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
|
||||||
size_t total = 0;
|
size_t total = 0;
|
||||||
int read = 0;
|
int read = 0;
|
||||||
do {
|
do {
|
||||||
int bufferSize = fileInfo.uncompressed_size < INT16_MAX ? fileInfo.uncompressed_size : INT16_MAX;
|
unsigned bufferSize = fileInfo.uncompressed_size < INT16_MAX ? static_cast<unsigned>(fileInfo.uncompressed_size) : INT16_MAX;
|
||||||
void *buffer = malloc(bufferSize);
|
void *buffer = malloc(bufferSize);
|
||||||
read = unzReadCurrentFile(zip, buffer, bufferSize);
|
read = unzReadCurrentFile(zip, buffer, bufferSize);
|
||||||
if (read > 0) {
|
if (read > 0) {
|
||||||
|
@ -256,7 +245,12 @@ void IFCImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
|
||||||
|
|
||||||
// tell the reader for which types we need to simulate STEPs reverse indices
|
// tell the reader for which types we need to simulate STEPs reverse indices
|
||||||
static const char *const inverse_indices_to_track[] = {
|
static const char *const inverse_indices_to_track[] = {
|
||||||
"ifcrelcontainedinspatialstructure", "ifcrelaggregates", "ifcrelvoidselement", "ifcreldefinesbyproperties", "ifcpropertyset", "ifcstyleditem"
|
"ifcrelcontainedinspatialstructure",
|
||||||
|
"ifcrelaggregates",
|
||||||
|
"ifcrelvoidselement",
|
||||||
|
"ifcreldefinesbyproperties",
|
||||||
|
"ifcpropertyset",
|
||||||
|
"ifcstyleditem"
|
||||||
};
|
};
|
||||||
|
|
||||||
// feed the IFC schema into the reader and pre-parse all lines
|
// feed the IFC schema into the reader and pre-parse all lines
|
||||||
|
@ -928,4 +922,4 @@ void MakeTreeRelative(ConversionData &conv) {
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
#endif
|
#endif // ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||||
|
|
|
@ -87,8 +87,8 @@ public:
|
||||||
int cylindricalTessellation;
|
int cylindricalTessellation;
|
||||||
};
|
};
|
||||||
|
|
||||||
IFCImporter();
|
IFCImporter() = default;
|
||||||
~IFCImporter() override;
|
~IFCImporter() override = default;
|
||||||
|
|
||||||
// --------------------
|
// --------------------
|
||||||
bool CanRead(const std::string &pFile,
|
bool CanRead(const std::string &pFile,
|
||||||
|
|
|
@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
|
||||||
|
|
||||||
Copyright (c) 2006-2022, assimp team
|
Copyright (c) 2006-2022, assimp team
|
||||||
|
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use of this software in source and binary forms,
|
Redistribution and use of this software in source and binary forms,
|
||||||
|
@ -40,9 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @file IFCMaterial.cpp
|
/// @file IFCMaterial.cpp
|
||||||
* @brief Implementation of conversion routines to convert IFC materials to aiMaterial
|
/// @brief Implementation of conversion routines to convert IFC materials to aiMaterial
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||||
|
|
||||||
|
@ -174,7 +172,6 @@ unsigned int ProcessMaterials(uint64_t id, unsigned int prevMatId, ConversionDat
|
||||||
|
|
||||||
aiString name;
|
aiString name;
|
||||||
name.Set("<IFCDefault>");
|
name.Set("<IFCDefault>");
|
||||||
// ConvertColorToString( color, name);
|
|
||||||
|
|
||||||
// look if there's already a default material with this base color
|
// look if there's already a default material with this base color
|
||||||
for( size_t a = 0; a < conv.materials.size(); ++a ) {
|
for( size_t a = 0; a < conv.materials.size(); ++a ) {
|
||||||
|
|
|
@ -38,24 +38,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @file IFCOpenings.cpp
|
/// @file IFCOpenings.cpp
|
||||||
* @brief Implements a subset of Ifc CSG operations for pouring
|
/// @brief Implements a subset of Ifc CSG operations for pouring
|
||||||
* holes for windows and doors into walls.
|
/// holes for windows and doors into walls.
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||||
|
|
||||||
#include "IFCUtil.h"
|
#include "IFCUtil.h"
|
||||||
#include "Common/PolyTools.h"
|
#include "Common/PolyTools.h"
|
||||||
#include "PostProcessing/ProcessHelper.h"
|
#include "PostProcessing/ProcessHelper.h"
|
||||||
|
#include "contrib/poly2tri/poly2tri/poly2tri.h"
|
||||||
#ifdef ASSIMP_USE_HUNTER
|
#include "contrib/clipper/clipper.hpp"
|
||||||
# include <poly2tri/poly2tri.h>
|
|
||||||
# include <polyclipping/clipper.hpp>
|
|
||||||
#else
|
|
||||||
# include "../contrib/poly2tri/poly2tri/poly2tri.h"
|
|
||||||
# include "../contrib/clipper/clipper.hpp"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <forward_list>
|
#include <forward_list>
|
||||||
|
@ -66,29 +59,37 @@ namespace Assimp {
|
||||||
namespace IFC {
|
namespace IFC {
|
||||||
|
|
||||||
using ClipperLib::ulong64;
|
using ClipperLib::ulong64;
|
||||||
|
|
||||||
// XXX use full -+ range ...
|
// XXX use full -+ range ...
|
||||||
const ClipperLib::long64 max_ulong64 = 1518500249; // clipper.cpp / hiRange var
|
const ClipperLib::long64 max_ulong64 = 1518500249; // clipper.cpp / hiRange var
|
||||||
|
|
||||||
//#define to_int64(p) (static_cast<ulong64>( std::max( 0., std::min( static_cast<IfcFloat>((p)), 1.) ) * max_ulong64 ))
|
AI_FORCE_INLINE ulong64 to_int64(IfcFloat p) {
|
||||||
#define to_int64(p) (static_cast<ulong64>(static_cast<IfcFloat>((p) ) * max_ulong64 ))
|
return (static_cast<ulong64>(static_cast<IfcFloat>((p) ) * max_ulong64 ));
|
||||||
#define from_int64(p) (static_cast<IfcFloat>((p)) / max_ulong64)
|
}
|
||||||
#define one_vec (IfcVector2(static_cast<IfcFloat>(1.0),static_cast<IfcFloat>(1.0)))
|
|
||||||
|
|
||||||
|
AI_FORCE_INLINE IfcFloat from_int64(ulong64 p) {
|
||||||
|
return (static_cast<IfcFloat>((p)) / max_ulong64);
|
||||||
|
}
|
||||||
|
|
||||||
|
AI_FORCE_INLINE void fillRectangle(const IfcVector2& pmin, const IfcVector2& pmax, std::vector<IfcVector2>& out) {
|
||||||
|
out.emplace_back(pmin.x, pmin.y);
|
||||||
|
out.emplace_back(pmin.x, pmax.y);
|
||||||
|
out.emplace_back(pmax.x, pmax.y);
|
||||||
|
out.emplace_back(pmax.x, pmin.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
const IfcVector2 one_vec(IfcVector2(static_cast<IfcFloat>(1.0),static_cast<IfcFloat>(1.0)));
|
||||||
|
|
||||||
// fallback method to generate wall openings
|
// fallback method to generate wall openings
|
||||||
bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,
|
bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings, TempMesh& curmesh);
|
||||||
TempMesh& curmesh);
|
|
||||||
|
|
||||||
|
|
||||||
typedef std::pair< IfcVector2, IfcVector2 > BoundingBox;
|
|
||||||
typedef std::map<IfcVector2,size_t,XYSorter> XYSortedField;
|
|
||||||
|
|
||||||
|
using BoundingBox = std::pair< IfcVector2, IfcVector2 >;
|
||||||
|
using XYSortedField = std::map<IfcVector2,size_t,XYSorter>;
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void QuadrifyPart(const IfcVector2& pmin, const IfcVector2& pmax, XYSortedField& field,
|
void QuadrifyPart(const IfcVector2& pmin, const IfcVector2& pmax, XYSortedField& field,
|
||||||
const std::vector< BoundingBox >& bbs,
|
const std::vector< BoundingBox >& bbs,
|
||||||
std::vector<IfcVector2>& out)
|
std::vector<IfcVector2>& out) {
|
||||||
{
|
|
||||||
if (!(pmin.x-pmax.x) || !(pmin.y-pmax.y)) {
|
if (!(pmin.x-pmax.x) || !(pmin.y-pmax.y)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -114,10 +115,7 @@ void QuadrifyPart(const IfcVector2& pmin, const IfcVector2& pmax, XYSortedField&
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
// the rectangle [pmin,pend] is opaque, fill it
|
// the rectangle [pmin,pend] is opaque, fill it
|
||||||
out.push_back(pmin);
|
fillRectangle(pmin, pmax, out);
|
||||||
out.emplace_back(pmin.x,pmax.y);
|
|
||||||
out.push_back(pmax);
|
|
||||||
out.emplace_back(pmax.x,pmin.y);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,19 +140,12 @@ void QuadrifyPart(const IfcVector2& pmin, const IfcVector2& pmax, XYSortedField&
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bb.second.y > ylast) {
|
if (bb.second.y > ylast) {
|
||||||
|
|
||||||
found = true;
|
found = true;
|
||||||
const IfcFloat ys = std::max(bb.first.y,pmin.y), ye = std::min(bb.second.y,pmax.y);
|
const IfcFloat ys = std::max(bb.first.y,pmin.y), ye = std::min(bb.second.y,pmax.y);
|
||||||
if (ys - ylast > 0.0f) {
|
if (ys - ylast > 0.0f) {
|
||||||
QuadrifyPart( IfcVector2(xs,ylast), IfcVector2(xe,ys) ,field,bbs,out);
|
QuadrifyPart( IfcVector2(xs,ylast), IfcVector2(xe,ys) ,field,bbs,out);
|
||||||
}
|
}
|
||||||
|
|
||||||
// the following are the window vertices
|
|
||||||
|
|
||||||
/*wnd.push_back(IfcVector2(xs,ys));
|
|
||||||
wnd.push_back(IfcVector2(xs,ye));
|
|
||||||
wnd.push_back(IfcVector2(xe,ye));
|
|
||||||
wnd.push_back(IfcVector2(xe,ys));*/
|
|
||||||
ylast = ye;
|
ylast = ye;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,23 +167,19 @@ void QuadrifyPart(const IfcVector2& pmin, const IfcVector2& pmax, XYSortedField&
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef std::vector<IfcVector2> Contour;
|
using Contour = std::vector<IfcVector2>;
|
||||||
typedef std::vector<bool> SkipList; // should probably use int for performance reasons
|
using SkipList = std::vector<bool>; // should probably use int for performance reasons
|
||||||
|
|
||||||
struct ProjectedWindowContour
|
struct ProjectedWindowContour {
|
||||||
{
|
|
||||||
Contour contour;
|
Contour contour;
|
||||||
BoundingBox bb;
|
BoundingBox bb;
|
||||||
SkipList skiplist;
|
SkipList skiplist;
|
||||||
bool is_rectangular;
|
bool is_rectangular;
|
||||||
|
|
||||||
|
|
||||||
ProjectedWindowContour(const Contour& contour, const BoundingBox& bb, bool is_rectangular)
|
ProjectedWindowContour(const Contour& contour, const BoundingBox& bb, bool is_rectangular)
|
||||||
: contour(contour)
|
: contour(contour), bb(bb) , is_rectangular(is_rectangular) {}
|
||||||
, bb(bb)
|
|
||||||
, is_rectangular(is_rectangular)
|
|
||||||
{}
|
|
||||||
|
|
||||||
|
~ProjectedWindowContour() = default;
|
||||||
|
|
||||||
bool IsInvalid() const {
|
bool IsInvalid() const {
|
||||||
return contour.empty();
|
return contour.empty();
|
||||||
|
@ -207,19 +194,17 @@ struct ProjectedWindowContour
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::vector< ProjectedWindowContour > ContourVector;
|
using ContourVector = std::vector<ProjectedWindowContour>;
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
bool BoundingBoxesOverlapping( const BoundingBox &ibb, const BoundingBox &bb )
|
static bool BoundingBoxesOverlapping( const BoundingBox &ibb, const BoundingBox &bb ) {
|
||||||
{
|
|
||||||
// count the '=' case as non-overlapping but as adjacent to each other
|
// count the '=' case as non-overlapping but as adjacent to each other
|
||||||
return ibb.first.x < bb.second.x && ibb.second.x > bb.first.x &&
|
return ibb.first.x < bb.second.x && ibb.second.x > bb.first.x &&
|
||||||
ibb.first.y < bb.second.y && ibb.second.y > bb.first.y;
|
ibb.first.y < bb.second.y && ibb.second.y > bb.first.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
bool IsDuplicateVertex(const IfcVector2& vv, const std::vector<IfcVector2>& temp_contour)
|
static bool IsDuplicateVertex(const IfcVector2& vv, const std::vector<IfcVector2>& temp_contour) {
|
||||||
{
|
|
||||||
// sanity check for duplicate vertices
|
// sanity check for duplicate vertices
|
||||||
for(const IfcVector2& cp : temp_contour) {
|
for(const IfcVector2& cp : temp_contour) {
|
||||||
if ((cp-vv).SquareLength() < 1e-5f) {
|
if ((cp-vv).SquareLength() < 1e-5f) {
|
||||||
|
@ -230,9 +215,8 @@ bool IsDuplicateVertex(const IfcVector2& vv, const std::vector<IfcVector2>& temp
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ExtractVerticesFromClipper(const ClipperLib::Polygon& poly, std::vector<IfcVector2>& temp_contour,
|
void ExtractVerticesFromClipper(const ClipperLib::Path& poly, std::vector<IfcVector2>& temp_contour,
|
||||||
bool filter_duplicates = false)
|
bool filter_duplicates = false) {
|
||||||
{
|
|
||||||
temp_contour.clear();
|
temp_contour.clear();
|
||||||
for(const ClipperLib::IntPoint& point : poly) {
|
for(const ClipperLib::IntPoint& point : poly) {
|
||||||
IfcVector2 vv = IfcVector2( from_int64(point.X), from_int64(point.Y));
|
IfcVector2 vv = IfcVector2( from_int64(point.X), from_int64(point.Y));
|
||||||
|
@ -246,8 +230,7 @@ void ExtractVerticesFromClipper(const ClipperLib::Polygon& poly, std::vector<Ifc
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
BoundingBox GetBoundingBox(const ClipperLib::Polygon& poly)
|
BoundingBox GetBoundingBox(const ClipperLib::Path& poly) {
|
||||||
{
|
|
||||||
IfcVector2 newbb_min, newbb_max;
|
IfcVector2 newbb_min, newbb_max;
|
||||||
MinMaxChooser<IfcVector2>()(newbb_min, newbb_max);
|
MinMaxChooser<IfcVector2>()(newbb_min, newbb_max);
|
||||||
|
|
||||||
|
@ -265,10 +248,7 @@ BoundingBox GetBoundingBox(const ClipperLib::Polygon& poly)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void InsertWindowContours(const ContourVector& contours,
|
void InsertWindowContours(const ContourVector& contours, const std::vector<TempOpening>& /*openings*/, TempMesh& curmesh) {
|
||||||
const std::vector<TempOpening>& /*openings*/,
|
|
||||||
TempMesh& curmesh)
|
|
||||||
{
|
|
||||||
// fix windows - we need to insert the real, polygonal shapes into the quadratic holes that we have now
|
// fix windows - we need to insert the real, polygonal shapes into the quadratic holes that we have now
|
||||||
for (size_t i = 0; i < contours.size(); ++i) {
|
for (size_t i = 0; i < contours.size(); ++i) {
|
||||||
const BoundingBox& bb = contours[i].bb;
|
const BoundingBox& bb = contours[i].bb;
|
||||||
|
@ -287,8 +267,7 @@ void InsertWindowContours(const ContourVector& contours,
|
||||||
const std::set<IfcVector2,XYSorter>::const_iterator end = verts.end();
|
const std::set<IfcVector2,XYSorter>::const_iterator end = verts.end();
|
||||||
if (verts.find(bb.first)!=end && verts.find(bb.second)!=end
|
if (verts.find(bb.first)!=end && verts.find(bb.second)!=end
|
||||||
&& verts.find(IfcVector2(bb.first.x,bb.second.y))!=end
|
&& verts.find(IfcVector2(bb.first.x,bb.second.y))!=end
|
||||||
&& verts.find(IfcVector2(bb.second.x,bb.first.y))!=end
|
&& verts.find(IfcVector2(bb.second.x,bb.first.y))!=end ) {
|
||||||
) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -313,8 +292,7 @@ void InsertWindowContours(const ContourVector& contours,
|
||||||
if (std::fabs(v.x-bb.first.x)<epsilon) {
|
if (std::fabs(v.x-bb.first.x)<epsilon) {
|
||||||
edge.x = bb.first.x;
|
edge.x = bb.first.x;
|
||||||
hit = true;
|
hit = true;
|
||||||
}
|
} else if (std::fabs(v.x-bb.second.x)<epsilon) {
|
||||||
else if (std::fabs(v.x-bb.second.x)<epsilon) {
|
|
||||||
edge.x = bb.second.x;
|
edge.x = bb.second.x;
|
||||||
hit = true;
|
hit = true;
|
||||||
}
|
}
|
||||||
|
@ -322,8 +300,7 @@ void InsertWindowContours(const ContourVector& contours,
|
||||||
if (std::fabs(v.y-bb.first.y)<epsilon) {
|
if (std::fabs(v.y-bb.first.y)<epsilon) {
|
||||||
edge.y = bb.first.y;
|
edge.y = bb.first.y;
|
||||||
hit = true;
|
hit = true;
|
||||||
}
|
} else if (std::fabs(v.y-bb.second.y)<epsilon) {
|
||||||
else if (std::fabs(v.y-bb.second.y)<epsilon) {
|
|
||||||
edge.y = bb.second.y;
|
edge.y = bb.second.y;
|
||||||
hit = true;
|
hit = true;
|
||||||
}
|
}
|
||||||
|
@ -347,26 +324,22 @@ void InsertWindowContours(const ContourVector& contours,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (edge != contour[last_hit]) {
|
if (edge != contour[last_hit]) {
|
||||||
|
|
||||||
IfcVector2 corner = edge;
|
IfcVector2 corner = edge;
|
||||||
|
|
||||||
if (std::fabs(contour[last_hit].x-bb.first.x)<epsilon) {
|
if (std::fabs(contour[last_hit].x-bb.first.x)<epsilon) {
|
||||||
corner.x = bb.first.x;
|
corner.x = bb.first.x;
|
||||||
}
|
} else if (std::fabs(contour[last_hit].x-bb.second.x)<epsilon) {
|
||||||
else if (std::fabs(contour[last_hit].x-bb.second.x)<epsilon) {
|
|
||||||
corner.x = bb.second.x;
|
corner.x = bb.second.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (std::fabs(contour[last_hit].y-bb.first.y)<epsilon) {
|
if (std::fabs(contour[last_hit].y-bb.first.y)<epsilon) {
|
||||||
corner.y = bb.first.y;
|
corner.y = bb.first.y;
|
||||||
}
|
} else if (std::fabs(contour[last_hit].y-bb.second.y)<epsilon) {
|
||||||
else if (std::fabs(contour[last_hit].y-bb.second.y)<epsilon) {
|
|
||||||
corner.y = bb.second.y;
|
corner.y = bb.second.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
curmesh.mVerts.emplace_back(corner.x, corner.y, 0.0f);
|
curmesh.mVerts.emplace_back(corner.x, corner.y, 0.0f);
|
||||||
}
|
} else if (cnt == 1) {
|
||||||
else if (cnt == 1) {
|
|
||||||
// avoid degenerate polygons (also known as lines or points)
|
// avoid degenerate polygons (also known as lines or points)
|
||||||
curmesh.mVerts.erase(curmesh.mVerts.begin()+old,curmesh.mVerts.end());
|
curmesh.mVerts.erase(curmesh.mVerts.begin()+old,curmesh.mVerts.end());
|
||||||
}
|
}
|
||||||
|
@ -378,8 +351,7 @@ void InsertWindowContours(const ContourVector& contours,
|
||||||
if (n == very_first_hit) {
|
if (n == very_first_hit) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
very_first_hit = n;
|
very_first_hit = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,14 +362,12 @@ void InsertWindowContours(const ContourVector& contours,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void MergeWindowContours (const std::vector<IfcVector2>& a,
|
void MergeWindowContours (const std::vector<IfcVector2>& a, const std::vector<IfcVector2>& b,
|
||||||
const std::vector<IfcVector2>& b,
|
ClipperLib::Paths& out) {
|
||||||
ClipperLib::ExPolygons& out)
|
|
||||||
{
|
|
||||||
out.clear();
|
out.clear();
|
||||||
|
|
||||||
ClipperLib::Clipper clipper;
|
ClipperLib::Clipper clipper;
|
||||||
ClipperLib::Polygon clip;
|
ClipperLib::Path clip;
|
||||||
|
|
||||||
for(const IfcVector2& pip : a) {
|
for(const IfcVector2& pip : a) {
|
||||||
clip.emplace_back(to_int64(pip.x), to_int64(pip.y));
|
clip.emplace_back(to_int64(pip.x), to_int64(pip.y));
|
||||||
|
@ -407,7 +377,7 @@ void MergeWindowContours (const std::vector<IfcVector2>& a,
|
||||||
std::reverse(clip.begin(), clip.end());
|
std::reverse(clip.begin(), clip.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
clipper.AddPolygon(clip, ClipperLib::ptSubject);
|
clipper.AddPath(clip, ClipperLib::ptSubject, true);
|
||||||
clip.clear();
|
clip.clear();
|
||||||
|
|
||||||
for(const IfcVector2& pip : b) {
|
for(const IfcVector2& pip : b) {
|
||||||
|
@ -418,7 +388,7 @@ void MergeWindowContours (const std::vector<IfcVector2>& a,
|
||||||
std::reverse(clip.begin(), clip.end());
|
std::reverse(clip.begin(), clip.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
clipper.AddPolygon(clip, ClipperLib::ptSubject);
|
clipper.AddPath(clip, ClipperLib::ptSubject, true);
|
||||||
clipper.Execute(ClipperLib::ctUnion, out,ClipperLib::pftNonZero,ClipperLib::pftNonZero);
|
clipper.Execute(ClipperLib::ctUnion, out,ClipperLib::pftNonZero,ClipperLib::pftNonZero);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -426,13 +396,11 @@ void MergeWindowContours (const std::vector<IfcVector2>& a,
|
||||||
// Subtract a from b
|
// Subtract a from b
|
||||||
void MakeDisjunctWindowContours (const std::vector<IfcVector2>& a,
|
void MakeDisjunctWindowContours (const std::vector<IfcVector2>& a,
|
||||||
const std::vector<IfcVector2>& b,
|
const std::vector<IfcVector2>& b,
|
||||||
ClipperLib::ExPolygons& out)
|
ClipperLib::Paths& out) {
|
||||||
{
|
|
||||||
out.clear();
|
out.clear();
|
||||||
|
|
||||||
ClipperLib::Clipper clipper;
|
ClipperLib::Clipper clipper;
|
||||||
ClipperLib::Polygon clip;
|
ClipperLib::Path clip;
|
||||||
|
|
||||||
for(const IfcVector2& pip : a) {
|
for(const IfcVector2& pip : a) {
|
||||||
clip.emplace_back(to_int64(pip.x), to_int64(pip.y));
|
clip.emplace_back(to_int64(pip.x), to_int64(pip.y));
|
||||||
}
|
}
|
||||||
|
@ -441,7 +409,7 @@ void MakeDisjunctWindowContours (const std::vector<IfcVector2>& a,
|
||||||
std::reverse(clip.begin(), clip.end());
|
std::reverse(clip.begin(), clip.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
clipper.AddPolygon(clip, ClipperLib::ptClip);
|
clipper.AddPath(clip, ClipperLib::ptClip, true);
|
||||||
clip.clear();
|
clip.clear();
|
||||||
|
|
||||||
for(const IfcVector2& pip : b) {
|
for(const IfcVector2& pip : b) {
|
||||||
|
@ -452,30 +420,28 @@ void MakeDisjunctWindowContours (const std::vector<IfcVector2>& a,
|
||||||
std::reverse(clip.begin(), clip.end());
|
std::reverse(clip.begin(), clip.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
clipper.AddPolygon(clip, ClipperLib::ptSubject);
|
clipper.AddPath(clip, ClipperLib::ptSubject, true);
|
||||||
clipper.Execute(ClipperLib::ctDifference, out,ClipperLib::pftNonZero,ClipperLib::pftNonZero);
|
clipper.Execute(ClipperLib::ctDifference, out,ClipperLib::pftNonZero,ClipperLib::pftNonZero);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void CleanupWindowContour(ProjectedWindowContour& window)
|
void CleanupWindowContour(ProjectedWindowContour& window) {
|
||||||
{
|
|
||||||
std::vector<IfcVector2> scratch;
|
std::vector<IfcVector2> scratch;
|
||||||
std::vector<IfcVector2>& contour = window.contour;
|
std::vector<IfcVector2>& contour = window.contour;
|
||||||
|
|
||||||
ClipperLib::Polygon subject;
|
ClipperLib::Path subject;
|
||||||
ClipperLib::Clipper clipper;
|
ClipperLib::Clipper clipper;
|
||||||
ClipperLib::ExPolygons clipped;
|
ClipperLib::Paths clipped;
|
||||||
|
|
||||||
for(const IfcVector2& pip : contour) {
|
for(const IfcVector2& pip : contour) {
|
||||||
subject.emplace_back(to_int64(pip.x), to_int64(pip.y));
|
subject.emplace_back(to_int64(pip.x), to_int64(pip.y));
|
||||||
}
|
}
|
||||||
|
|
||||||
clipper.AddPolygon(subject,ClipperLib::ptSubject);
|
clipper.AddPath(subject,ClipperLib::ptSubject, true);
|
||||||
clipper.Execute(ClipperLib::ctUnion,clipped,ClipperLib::pftNonZero,ClipperLib::pftNonZero);
|
clipper.Execute(ClipperLib::ctUnion,clipped,ClipperLib::pftNonZero,ClipperLib::pftNonZero);
|
||||||
|
|
||||||
// This should yield only one polygon or something went wrong
|
// This should yield only one polygon or something went wrong
|
||||||
if (clipped.size() != 1) {
|
if (clipped.size() != 1) {
|
||||||
|
|
||||||
// Empty polygon? drop the contour altogether
|
// Empty polygon? drop the contour altogether
|
||||||
if(clipped.empty()) {
|
if(clipped.empty()) {
|
||||||
IFCImporter::LogError("error during polygon clipping, window contour is degenerate");
|
IFCImporter::LogError("error during polygon clipping, window contour is degenerate");
|
||||||
|
@ -487,28 +453,25 @@ void CleanupWindowContour(ProjectedWindowContour& window)
|
||||||
IFCImporter::LogError("error during polygon clipping, window contour is not convex");
|
IFCImporter::LogError("error during polygon clipping, window contour is not convex");
|
||||||
}
|
}
|
||||||
|
|
||||||
ExtractVerticesFromClipper(clipped[0].outer, scratch);
|
|
||||||
// Assume the bounding box doesn't change during this operation
|
// Assume the bounding box doesn't change during this operation
|
||||||
|
ExtractVerticesFromClipper(clipped[0], scratch);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void CleanupWindowContours(ContourVector& contours)
|
void CleanupWindowContours(ContourVector& contours) {
|
||||||
{
|
|
||||||
// Use PolyClipper to clean up window contours
|
// Use PolyClipper to clean up window contours
|
||||||
try {
|
try {
|
||||||
for(ProjectedWindowContour& window : contours) {
|
for(ProjectedWindowContour& window : contours) {
|
||||||
CleanupWindowContour(window);
|
CleanupWindowContour(window);
|
||||||
}
|
}
|
||||||
}
|
} catch (const char* sx) {
|
||||||
catch (const char* sx) {
|
|
||||||
IFCImporter::LogError("error during polygon clipping, window shape may be wrong: (Clipper: "
|
IFCImporter::LogError("error during polygon clipping, window shape may be wrong: (Clipper: "
|
||||||
+ std::string(sx) + ")");
|
+ std::string(sx) + ")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void CleanupOuterContour(const std::vector<IfcVector2>& contour_flat, TempMesh& curmesh)
|
void CleanupOuterContour(const std::vector<IfcVector2>& contour_flat, TempMesh& curmesh) {
|
||||||
{
|
|
||||||
std::vector<IfcVector3> vold;
|
std::vector<IfcVector3> vold;
|
||||||
std::vector<unsigned int> iold;
|
std::vector<unsigned int> iold;
|
||||||
|
|
||||||
|
@ -517,12 +480,11 @@ void CleanupOuterContour(const std::vector<IfcVector2>& contour_flat, TempMesh&
|
||||||
|
|
||||||
// Fix the outer contour using polyclipper
|
// Fix the outer contour using polyclipper
|
||||||
try {
|
try {
|
||||||
|
ClipperLib::Path subject;
|
||||||
ClipperLib::Polygon subject;
|
|
||||||
ClipperLib::Clipper clipper;
|
ClipperLib::Clipper clipper;
|
||||||
ClipperLib::ExPolygons clipped;
|
ClipperLib::Paths clipped;
|
||||||
|
|
||||||
ClipperLib::Polygon clip;
|
ClipperLib::Path clip;
|
||||||
clip.reserve(contour_flat.size());
|
clip.reserve(contour_flat.size());
|
||||||
for(const IfcVector2& pip : contour_flat) {
|
for(const IfcVector2& pip : contour_flat) {
|
||||||
clip.emplace_back(to_int64(pip.x), to_int64(pip.y));
|
clip.emplace_back(to_int64(pip.x), to_int64(pip.y));
|
||||||
|
@ -551,18 +513,15 @@ void CleanupOuterContour(const std::vector<IfcVector2>& contour_flat, TempMesh&
|
||||||
std::reverse(subject.begin(), subject.end());
|
std::reverse(subject.begin(), subject.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
clipper.AddPolygon(subject,ClipperLib::ptSubject);
|
clipper.AddPath(subject,ClipperLib::ptSubject, true);
|
||||||
clipper.AddPolygon(clip,ClipperLib::ptClip);
|
clipper.AddPath(clip,ClipperLib::ptClip, true);
|
||||||
|
|
||||||
clipper.Execute(ClipperLib::ctIntersection,clipped,ClipperLib::pftNonZero,ClipperLib::pftNonZero);
|
clipper.Execute(ClipperLib::ctIntersection,clipped,ClipperLib::pftNonZero,ClipperLib::pftNonZero);
|
||||||
|
|
||||||
for(const ClipperLib::ExPolygon& ex : clipped) {
|
for(const ClipperLib::Path& ex : clipped) {
|
||||||
iold.push_back(static_cast<unsigned int>(ex.outer.size()));
|
iold.push_back(static_cast<unsigned int>(ex.size()));
|
||||||
for(const ClipperLib::IntPoint& point : ex.outer) {
|
for(const ClipperLib::IntPoint& point : ex) {
|
||||||
vold.emplace_back(
|
vold.emplace_back(from_int64(point.X), from_int64(point.Y), 0.0f);
|
||||||
from_int64(point.X),
|
|
||||||
from_int64(point.Y),
|
|
||||||
0.0f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -571,8 +530,7 @@ void CleanupOuterContour(const std::vector<IfcVector2>& contour_flat, TempMesh&
|
||||||
clipper.Clear();
|
clipper.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (const char* sx) {
|
||||||
catch (const char* sx) {
|
|
||||||
IFCImporter::LogError("Ifc: error during polygon clipping, wall contour line may be wrong: (Clipper: "
|
IFCImporter::LogError("Ifc: error during polygon clipping, wall contour line may be wrong: (Clipper: "
|
||||||
+ std::string(sx) + ")");
|
+ std::string(sx) + ")");
|
||||||
|
|
||||||
|
@ -584,17 +542,14 @@ void CleanupOuterContour(const std::vector<IfcVector2>& contour_flat, TempMesh&
|
||||||
std::swap(iold,curmesh.mVertcnt);
|
std::swap(iold,curmesh.mVertcnt);
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef std::vector<TempOpening*> OpeningRefs;
|
using OpeningRefs = std::vector<TempOpening*> ;
|
||||||
typedef std::vector<OpeningRefs > OpeningRefVector;
|
using OpeningRefVector = std::vector<OpeningRefs >;
|
||||||
|
using ContourRefVector = std::vector<std::pair<
|
||||||
typedef std::vector<std::pair<
|
|
||||||
ContourVector::const_iterator,
|
ContourVector::const_iterator,
|
||||||
Contour::const_iterator>
|
Contour::const_iterator> >;
|
||||||
> ContourRefVector;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
bool BoundingBoxesAdjacent(const BoundingBox& bb, const BoundingBox& ibb)
|
bool BoundingBoxesAdjacent(const BoundingBox& bb, const BoundingBox& ibb) {
|
||||||
{
|
|
||||||
// TODO: I'm pretty sure there is a much more compact way to check this
|
// TODO: I'm pretty sure there is a much more compact way to check this
|
||||||
const IfcFloat epsilon = Math::getEpsilon<float>();
|
const IfcFloat epsilon = Math::getEpsilon<float>();
|
||||||
return (std::fabs(bb.second.x - ibb.first.x) < epsilon && bb.first.y <= ibb.second.y && bb.second.y >= ibb.first.y) ||
|
return (std::fabs(bb.second.x - ibb.first.x) < epsilon && bb.first.y <= ibb.second.y && bb.second.y >= ibb.first.y) ||
|
||||||
|
@ -608,8 +563,7 @@ bool BoundingBoxesAdjacent(const BoundingBox& bb, const BoundingBox& ibb)
|
||||||
// output the intersection points on n0,n1
|
// output the intersection points on n0,n1
|
||||||
bool IntersectingLineSegments(const IfcVector2& n0, const IfcVector2& n1,
|
bool IntersectingLineSegments(const IfcVector2& n0, const IfcVector2& n1,
|
||||||
const IfcVector2& m0, const IfcVector2& m1,
|
const IfcVector2& m0, const IfcVector2& m1,
|
||||||
IfcVector2& out0, IfcVector2& out1)
|
IfcVector2& out0, IfcVector2& out1) {
|
||||||
{
|
|
||||||
const IfcVector2 n0_to_n1 = n1 - n0;
|
const IfcVector2 n0_to_n1 = n1 - n0;
|
||||||
|
|
||||||
const IfcVector2 n0_to_m0 = m0 - n0;
|
const IfcVector2 n0_to_m0 = m0 - n0;
|
||||||
|
@ -648,8 +602,7 @@ bool IntersectingLineSegments(const IfcVector2& n0, const IfcVector2& n1,
|
||||||
if (std::fabs(s1) == inf && std::fabs(n0_to_m1.x) < smalle) {
|
if (std::fabs(s1) == inf && std::fabs(n0_to_m1.x) < smalle) {
|
||||||
s1 = 0.;
|
s1 = 0.;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
s0 = n0_to_m0.y / n0_to_n1.y;
|
s0 = n0_to_m0.y / n0_to_n1.y;
|
||||||
s1 = n0_to_m1.y / n0_to_n1.y;
|
s1 = n0_to_m1.y / n0_to_n1.y;
|
||||||
|
|
||||||
|
@ -682,8 +635,7 @@ bool IntersectingLineSegments(const IfcVector2& n0, const IfcVector2& n1,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void FindAdjacentContours(ContourVector::iterator current, const ContourVector& contours)
|
void FindAdjacentContours(ContourVector::iterator current, const ContourVector& contours) {
|
||||||
{
|
|
||||||
const IfcFloat sqlen_epsilon = static_cast<IfcFloat>(Math::getEpsilon<float>());
|
const IfcFloat sqlen_epsilon = static_cast<IfcFloat>(Math::getEpsilon<float>());
|
||||||
const BoundingBox& bb = (*current).bb;
|
const BoundingBox& bb = (*current).bb;
|
||||||
|
|
||||||
|
@ -698,13 +650,6 @@ void FindAdjacentContours(ContourVector::iterator current, const ContourVector&
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this left here to make clear we also run on the current contour
|
|
||||||
// to check for overlapping contour segments (which can happen due
|
|
||||||
// to projection artifacts).
|
|
||||||
//if(it == current) {
|
|
||||||
// continue;
|
|
||||||
//}
|
|
||||||
|
|
||||||
const bool is_me = it == current;
|
const bool is_me = it == current;
|
||||||
|
|
||||||
const BoundingBox& ibb = (*it).bb;
|
const BoundingBox& ibb = (*it).bb;
|
||||||
|
@ -740,8 +685,7 @@ void FindAdjacentContours(ContourVector::iterator current, const ContourVector&
|
||||||
|
|
||||||
ncontour.insert(ncontour.begin() + n, isect0);
|
ncontour.insert(ncontour.begin() + n, isect0);
|
||||||
skiplist.insert(skiplist.begin() + n, true);
|
skiplist.insert(skiplist.begin() + n, true);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
skiplist[n] = true;
|
skiplist[n] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -759,15 +703,13 @@ void FindAdjacentContours(ContourVector::iterator current, const ContourVector&
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
AI_FORCE_INLINE bool LikelyBorder(const IfcVector2& vdelta)
|
AI_FORCE_INLINE bool LikelyBorder(const IfcVector2& vdelta) {
|
||||||
{
|
|
||||||
const IfcFloat dot_point_epsilon = static_cast<IfcFloat>(Math::getEpsilon<float>());
|
const IfcFloat dot_point_epsilon = static_cast<IfcFloat>(Math::getEpsilon<float>());
|
||||||
return std::fabs(vdelta.x * vdelta.y) < dot_point_epsilon;
|
return std::fabs(vdelta.x * vdelta.y) < dot_point_epsilon;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void FindBorderContours(ContourVector::iterator current)
|
void FindBorderContours(ContourVector::iterator current) {
|
||||||
{
|
|
||||||
const IfcFloat border_epsilon_upper = static_cast<IfcFloat>(1-1e-4);
|
const IfcFloat border_epsilon_upper = static_cast<IfcFloat>(1-1e-4);
|
||||||
const IfcFloat border_epsilon_lower = static_cast<IfcFloat>(1e-4);
|
const IfcFloat border_epsilon_lower = static_cast<IfcFloat>(1e-4);
|
||||||
|
|
||||||
|
@ -787,20 +729,17 @@ void FindBorderContours(ContourVector::iterator current)
|
||||||
// not have any geometry to close them (think of door openings).
|
// not have any geometry to close them (think of door openings).
|
||||||
if (proj_point.x <= border_epsilon_lower || proj_point.x >= border_epsilon_upper ||
|
if (proj_point.x <= border_epsilon_lower || proj_point.x >= border_epsilon_upper ||
|
||||||
proj_point.y <= border_epsilon_lower || proj_point.y >= border_epsilon_upper) {
|
proj_point.y <= border_epsilon_lower || proj_point.y >= border_epsilon_upper) {
|
||||||
|
|
||||||
if (outer_border) {
|
if (outer_border) {
|
||||||
ai_assert(cit != cbegin);
|
ai_assert(cit != cbegin);
|
||||||
if (LikelyBorder(proj_point - last_proj_point)) {
|
if (LikelyBorder(proj_point - last_proj_point)) {
|
||||||
skiplist[std::distance(cbegin, cit) - 1] = true;
|
skiplist[std::distance(cbegin, cit) - 1] = true;
|
||||||
}
|
}
|
||||||
}
|
} else if (cit == cbegin) {
|
||||||
else if (cit == cbegin) {
|
|
||||||
start_on_outer_border = true;
|
start_on_outer_border = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
outer_border = true;
|
outer_border = true;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
outer_border = false;
|
outer_border = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -817,16 +756,14 @@ void FindBorderContours(ContourVector::iterator current)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
AI_FORCE_INLINE bool LikelyDiagonal(IfcVector2 vdelta)
|
AI_FORCE_INLINE bool LikelyDiagonal(IfcVector2 vdelta) {
|
||||||
{
|
|
||||||
vdelta.x = std::fabs(vdelta.x);
|
vdelta.x = std::fabs(vdelta.x);
|
||||||
vdelta.y = std::fabs(vdelta.y);
|
vdelta.y = std::fabs(vdelta.y);
|
||||||
return (std::fabs(vdelta.x-vdelta.y) < 0.8 * std::max(vdelta.x, vdelta.y));
|
return (std::fabs(vdelta.x-vdelta.y) < 0.8 * std::max(vdelta.x, vdelta.y));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void FindLikelyCrossingLines(ContourVector::iterator current)
|
void FindLikelyCrossingLines(ContourVector::iterator current) {
|
||||||
{
|
|
||||||
SkipList& skiplist = (*current).skiplist;
|
SkipList& skiplist = (*current).skiplist;
|
||||||
IfcVector2 last_proj_point;
|
IfcVector2 last_proj_point;
|
||||||
|
|
||||||
|
@ -854,8 +791,7 @@ void FindLikelyCrossingLines(ContourVector::iterator current)
|
||||||
size_t CloseWindows(ContourVector& contours,
|
size_t CloseWindows(ContourVector& contours,
|
||||||
const IfcMatrix4& minv,
|
const IfcMatrix4& minv,
|
||||||
OpeningRefVector& contours_to_openings,
|
OpeningRefVector& contours_to_openings,
|
||||||
TempMesh& curmesh)
|
TempMesh& curmesh) {
|
||||||
{
|
|
||||||
size_t closed = 0;
|
size_t closed = 0;
|
||||||
// For all contour points, check if one of the assigned openings does
|
// For all contour points, check if one of the assigned openings does
|
||||||
// already have points assigned to it. In this case, assume this is
|
// already have points assigned to it. In this case, assume this is
|
||||||
|
@ -964,8 +900,7 @@ size_t CloseWindows(ContourVector& contours,
|
||||||
if (drop_this_edge) {
|
if (drop_this_edge) {
|
||||||
curmesh.mVerts.pop_back();
|
curmesh.mVerts.pop_back();
|
||||||
curmesh.mVerts.pop_back();
|
curmesh.mVerts.pop_back();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
curmesh.mVerts.push_back(((cit == cbegin) != reverseCountourFaces) ? world_point : bestv);
|
curmesh.mVerts.push_back(((cit == cbegin) != reverseCountourFaces) ? world_point : bestv);
|
||||||
curmesh.mVerts.push_back(((cit == cbegin) != reverseCountourFaces) ? bestv : world_point);
|
curmesh.mVerts.push_back(((cit == cbegin) != reverseCountourFaces) ? bestv : world_point);
|
||||||
|
|
||||||
|
@ -992,16 +927,13 @@ size_t CloseWindows(ContourVector& contours,
|
||||||
curmesh.mVertcnt.pop_back();
|
curmesh.mVertcnt.pop_back();
|
||||||
curmesh.mVerts.pop_back();
|
curmesh.mVerts.pop_back();
|
||||||
curmesh.mVerts.pop_back();
|
curmesh.mVerts.pop_back();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
curmesh.mVerts.push_back(reverseCountourFaces ? start0 : start1);
|
curmesh.mVerts.push_back(reverseCountourFaces ? start0 : start1);
|
||||||
curmesh.mVerts.push_back(reverseCountourFaces ? start1 : start0);
|
curmesh.mVerts.push_back(reverseCountourFaces ? start1 : start0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
|
|
||||||
const Contour::const_iterator cbegin = (*it).contour.begin(), cend = (*it).contour.end();
|
const Contour::const_iterator cbegin = (*it).contour.begin(), cend = (*it).contour.end();
|
||||||
for(TempOpening* opening : refs) {
|
for(TempOpening* opening : refs) {
|
||||||
ai_assert(opening->wallPoints.empty());
|
ai_assert(opening->wallPoints.empty());
|
||||||
|
@ -1018,8 +950,7 @@ size_t CloseWindows(ContourVector& contours,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void Quadrify(const std::vector< BoundingBox >& bbs, TempMesh& curmesh)
|
void Quadrify(const std::vector< BoundingBox >& bbs, TempMesh& curmesh) {
|
||||||
{
|
|
||||||
ai_assert(curmesh.IsEmpty());
|
ai_assert(curmesh.IsEmpty());
|
||||||
|
|
||||||
std::vector<IfcVector2> quads;
|
std::vector<IfcVector2> quads;
|
||||||
|
@ -1045,8 +976,7 @@ void Quadrify(const std::vector< BoundingBox >& bbs, TempMesh& curmesh)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void Quadrify(const ContourVector& contours, TempMesh& curmesh)
|
void Quadrify(const ContourVector& contours, TempMesh& curmesh) {
|
||||||
{
|
|
||||||
std::vector<BoundingBox> bbs;
|
std::vector<BoundingBox> bbs;
|
||||||
bbs.reserve(contours.size());
|
bbs.reserve(contours.size());
|
||||||
|
|
||||||
|
@ -1058,12 +988,17 @@ void Quadrify(const ContourVector& contours, TempMesh& curmesh)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
IfcMatrix4 ProjectOntoPlane(std::vector<IfcVector2>& out_contour, const TempMesh& in_mesh,
|
IfcMatrix4 ProjectOntoPlane(std::vector<IfcVector2>& out_contour,
|
||||||
bool &ok, IfcVector3& nor_out)
|
const TempMesh& in_mesh,
|
||||||
{
|
bool &ok,
|
||||||
|
IfcVector3& nor_out) {
|
||||||
const std::vector<IfcVector3>& in_verts = in_mesh.mVerts;
|
const std::vector<IfcVector3>& in_verts = in_mesh.mVerts;
|
||||||
ok = true;
|
if (in_verts.empty()){
|
||||||
|
ok = false;
|
||||||
|
return IfcMatrix4();
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = true;
|
||||||
IfcMatrix4 m = IfcMatrix4(DerivePlaneCoordinateSpace(in_mesh, ok, nor_out));
|
IfcMatrix4 m = IfcMatrix4(DerivePlaneCoordinateSpace(in_mesh, ok, nor_out));
|
||||||
if(!ok) {
|
if(!ok) {
|
||||||
return IfcMatrix4();
|
return IfcMatrix4();
|
||||||
|
@ -1076,7 +1011,6 @@ IfcMatrix4 ProjectOntoPlane(std::vector<IfcVector2>& out_contour, const TempMesh
|
||||||
IfcFloat zcoord = 0;
|
IfcFloat zcoord = 0;
|
||||||
out_contour.reserve(in_verts.size());
|
out_contour.reserve(in_verts.size());
|
||||||
|
|
||||||
|
|
||||||
IfcVector3 vmin, vmax;
|
IfcVector3 vmin, vmax;
|
||||||
MinMaxChooser<IfcVector3>()(vmin, vmax);
|
MinMaxChooser<IfcVector3>()(vmin, vmax);
|
||||||
|
|
||||||
|
@ -1087,11 +1021,6 @@ IfcMatrix4 ProjectOntoPlane(std::vector<IfcVector2>& out_contour, const TempMesh
|
||||||
// (which are present, of course), this should be the same value for
|
// (which are present, of course), this should be the same value for
|
||||||
// all polygon vertices (assuming the polygon is planar).
|
// all polygon vertices (assuming the polygon is planar).
|
||||||
|
|
||||||
// XXX this should be guarded, but we somehow need to pick a suitable
|
|
||||||
// epsilon
|
|
||||||
// if(coord != -1.0f) {
|
|
||||||
// assert(std::fabs(coord - vv.z) < 1e-3f);
|
|
||||||
// }
|
|
||||||
zcoord += vv.z;
|
zcoord += vv.z;
|
||||||
vmin = std::min(vv, vmin);
|
vmin = std::min(vv, vmin);
|
||||||
vmax = std::max(vv, vmax);
|
vmax = std::max(vv, vmax);
|
||||||
|
@ -1146,8 +1075,7 @@ bool GenerateOpenings(std::vector<TempOpening>& openings,
|
||||||
TempMesh& curmesh,
|
TempMesh& curmesh,
|
||||||
bool check_intersection,
|
bool check_intersection,
|
||||||
bool generate_connection_geometry,
|
bool generate_connection_geometry,
|
||||||
const IfcVector3& wall_extrusion_axis)
|
const IfcVector3& wall_extrusion_axis) {
|
||||||
{
|
|
||||||
OpeningRefVector contours_to_openings;
|
OpeningRefVector contours_to_openings;
|
||||||
|
|
||||||
// Try to derive a solid base plane within the current surface for use as
|
// Try to derive a solid base plane within the current surface for use as
|
||||||
|
@ -1183,8 +1111,7 @@ bool GenerateOpenings(std::vector<TempOpening>& openings,
|
||||||
IfcVector3 norm_extrusion_dir = opening.extrusionDir;
|
IfcVector3 norm_extrusion_dir = opening.extrusionDir;
|
||||||
if (norm_extrusion_dir.SquareLength() > 1e-10) {
|
if (norm_extrusion_dir.SquareLength() > 1e-10) {
|
||||||
norm_extrusion_dir.Normalize();
|
norm_extrusion_dir.Normalize();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
norm_extrusion_dir = IfcVector3();
|
norm_extrusion_dir = IfcVector3();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1249,10 +1176,8 @@ bool GenerateOpenings(std::vector<TempOpening>& openings,
|
||||||
const IfcVector3 v = m * x;
|
const IfcVector3 v = m * x;
|
||||||
IfcVector2 vv(v.x, v.y);
|
IfcVector2 vv(v.x, v.y);
|
||||||
|
|
||||||
//if(check_intersection) {
|
|
||||||
dmin = std::min(dmin, v.z);
|
dmin = std::min(dmin, v.z);
|
||||||
dmax = std::max(dmax, v.z);
|
dmax = std::max(dmax, v.z);
|
||||||
//}
|
|
||||||
|
|
||||||
// sanity rounding
|
// sanity rounding
|
||||||
vv = std::max(vv,IfcVector2());
|
vv = std::max(vv,IfcVector2());
|
||||||
|
@ -1261,8 +1186,7 @@ bool GenerateOpenings(std::vector<TempOpening>& openings,
|
||||||
if(side_flag) {
|
if(side_flag) {
|
||||||
vpmin = std::min(vpmin,vv);
|
vpmin = std::min(vpmin,vv);
|
||||||
vpmax = std::max(vpmax,vv);
|
vpmax = std::max(vpmax,vv);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
vpmin2 = std::min(vpmin2,vv);
|
vpmin2 = std::min(vpmin2,vv);
|
||||||
vpmax2 = std::max(vpmax2,vv);
|
vpmax2 = std::max(vpmax2,vv);
|
||||||
}
|
}
|
||||||
|
@ -1310,28 +1234,25 @@ bool GenerateOpenings(std::vector<TempOpening>& openings,
|
||||||
// See if this BB intersects or is in close adjacency to any other BB we have so far.
|
// See if this BB intersects or is in close adjacency to any other BB we have so far.
|
||||||
for (ContourVector::iterator it = contours.begin(); it != contours.end(); ) {
|
for (ContourVector::iterator it = contours.begin(); it != contours.end(); ) {
|
||||||
const BoundingBox& ibb = (*it).bb;
|
const BoundingBox& ibb = (*it).bb;
|
||||||
|
|
||||||
if (BoundingBoxesOverlapping(ibb, bb)) {
|
if (BoundingBoxesOverlapping(ibb, bb)) {
|
||||||
|
|
||||||
if (!(*it).is_rectangular) {
|
if (!(*it).is_rectangular) {
|
||||||
is_rectangle = false;
|
is_rectangle = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<IfcVector2>& other = (*it).contour;
|
const std::vector<IfcVector2>& other = (*it).contour;
|
||||||
ClipperLib::ExPolygons poly;
|
ClipperLib::Paths poly;
|
||||||
|
|
||||||
// First check whether subtracting the old contour (to which ibb belongs)
|
// First check whether subtracting the old contour (to which ibb belongs)
|
||||||
// from the new contour (to which bb belongs) yields an updated bb which
|
// from the new contour (to which bb belongs) yields an updated bb which
|
||||||
// no longer overlaps ibb
|
// no longer overlaps ibb
|
||||||
MakeDisjunctWindowContours(other, temp_contour, poly);
|
MakeDisjunctWindowContours(other, temp_contour, poly);
|
||||||
if(poly.size() == 1) {
|
if(poly.size() == 1) {
|
||||||
|
const BoundingBox newbb = GetBoundingBox(poly[0]);
|
||||||
const BoundingBox newbb = GetBoundingBox(poly[0].outer);
|
|
||||||
if (!BoundingBoxesOverlapping(ibb, newbb )) {
|
if (!BoundingBoxesOverlapping(ibb, newbb )) {
|
||||||
// Good guy bounding box
|
// Good guy bounding box
|
||||||
bb = newbb ;
|
bb = newbb ;
|
||||||
|
|
||||||
ExtractVerticesFromClipper(poly[0].outer, temp_contour, false);
|
ExtractVerticesFromClipper(poly[0], temp_contour, false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1343,15 +1264,13 @@ bool GenerateOpenings(std::vector<TempOpening>& openings,
|
||||||
|
|
||||||
if (poly.size() > 1) {
|
if (poly.size() > 1) {
|
||||||
return TryAddOpenings_Poly2Tri(openings, curmesh);
|
return TryAddOpenings_Poly2Tri(openings, curmesh);
|
||||||
}
|
} else if (poly.empty()) {
|
||||||
else if (poly.size() == 0) {
|
|
||||||
IFCImporter::LogWarn("ignoring duplicate opening");
|
IFCImporter::LogWarn("ignoring duplicate opening");
|
||||||
temp_contour.clear();
|
temp_contour.clear();
|
||||||
break;
|
break;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
IFCImporter::LogVerboseDebug("merging overlapping openings");
|
IFCImporter::LogVerboseDebug("merging overlapping openings");
|
||||||
ExtractVerticesFromClipper(poly[0].outer, temp_contour, false);
|
ExtractVerticesFromClipper(poly[0], temp_contour, false);
|
||||||
|
|
||||||
// Generate the union of the bounding boxes
|
// Generate the union of the bounding boxes
|
||||||
bb.first = std::min(bb.first, ibb.first);
|
bb.first = std::min(bb.first, ibb.first);
|
||||||
|
@ -1429,9 +1348,14 @@ bool GenerateOpenings(std::vector<TempOpening>& openings,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<IfcVector2> GetContourInPlane2D(const std::shared_ptr<TempMesh>& mesh,IfcMatrix3 planeSpace,
|
std::vector<IfcVector2> GetContourInPlane2D(const std::shared_ptr<TempMesh>& mesh,
|
||||||
IfcVector3 planeNor,IfcFloat planeOffset,
|
IfcMatrix3 planeSpace,
|
||||||
IfcVector3 extrusionDir,IfcVector3& wall_extrusion,bool& first,bool& ok) {
|
IfcVector3 planeNor,
|
||||||
|
IfcFloat planeOffset,
|
||||||
|
IfcVector3 extrusionDir,
|
||||||
|
IfcVector3& wall_extrusion,
|
||||||
|
bool& first,
|
||||||
|
bool& ok) {
|
||||||
std::vector<IfcVector2> contour;
|
std::vector<IfcVector2> contour;
|
||||||
|
|
||||||
const auto outernor = ((mesh->mVerts[2] - mesh->mVerts[0]) ^ (mesh->mVerts[1] - mesh->mVerts[0])).Normalize();
|
const auto outernor = ((mesh->mVerts[2] - mesh->mVerts[0]) ^ (mesh->mVerts[1] - mesh->mVerts[0])).Normalize();
|
||||||
|
@ -1448,7 +1372,7 @@ std::vector<IfcVector2> GetContourInPlane2D(const std::shared_ptr<TempMesh>& mes
|
||||||
const std::vector<IfcVector3>& va = mesh->mVerts;
|
const std::vector<IfcVector3>& va = mesh->mVerts;
|
||||||
if(va.size() <= 2) {
|
if(va.size() <= 2) {
|
||||||
std::stringstream msg;
|
std::stringstream msg;
|
||||||
msg << "Skipping: Only " << va.size() << " verticies in opening mesh.";
|
msg << "Skipping: Only " << va.size() << " vertices in opening mesh.";
|
||||||
IFCImporter::LogDebug(msg.str().c_str());
|
IFCImporter::LogDebug(msg.str().c_str());
|
||||||
ok = false;
|
ok = false;
|
||||||
return contour;
|
return contour;
|
||||||
|
@ -1494,7 +1418,6 @@ static void logSegment(std::pair<IfcVector2,IfcVector2> segment) {
|
||||||
|
|
||||||
std::vector<std::vector<IfcVector2>> GetContoursInPlane3D(const std::shared_ptr<TempMesh>& mesh,IfcMatrix3 planeSpace,
|
std::vector<std::vector<IfcVector2>> GetContoursInPlane3D(const std::shared_ptr<TempMesh>& mesh,IfcMatrix3 planeSpace,
|
||||||
IfcFloat planeOffset) {
|
IfcFloat planeOffset) {
|
||||||
|
|
||||||
{
|
{
|
||||||
std::stringstream msg;
|
std::stringstream msg;
|
||||||
msg << "GetContoursInPlane3D: planeSpace is \n";
|
msg << "GetContoursInPlane3D: planeSpace is \n";
|
||||||
|
@ -1521,8 +1444,8 @@ std::vector<std::vector<IfcVector2>> GetContoursInPlane3D(const std::shared_ptr<
|
||||||
IFCImporter::LogInfo(msg.str().c_str());
|
IFCImporter::LogInfo(msg.str().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if(nVertices <= 2) // not a plane, a point or line
|
// not a plane, a point or line
|
||||||
{
|
if(nVertices <= 2) {
|
||||||
std::stringstream msg;
|
std::stringstream msg;
|
||||||
msg << "GetContoursInPlane3D: found point or line when expecting plane (only " << nVertices << " vertices)";
|
msg << "GetContoursInPlane3D: found point or line when expecting plane (only " << nVertices << " vertices)";
|
||||||
IFCImporter::LogWarn(msg.str().c_str());
|
IFCImporter::LogWarn(msg.str().c_str());
|
||||||
|
@ -1552,15 +1475,12 @@ std::vector<std::vector<IfcVector2>> GetContoursInPlane3D(const std::shared_ptr<
|
||||||
if(std::fabs(vn.z - planeOffset) < close) {
|
if(std::fabs(vn.z - planeOffset) < close) {
|
||||||
// on the plane
|
// on the plane
|
||||||
intersection = vn;
|
intersection = vn;
|
||||||
}
|
} else if((vn.z > planeOffset) != (vp.z > planeOffset)) {
|
||||||
else if((vn.z > planeOffset) != (vp.z > planeOffset))
|
|
||||||
{
|
|
||||||
// passes through the plane
|
// passes through the plane
|
||||||
auto vdir = vn - vp;
|
auto vdir = vn - vp;
|
||||||
auto scale = (planeOffset - vp.z) / vdir.z;
|
auto scale = (planeOffset - vp.z) / vdir.z;
|
||||||
intersection = vp + scale * vdir;
|
intersection = vp + scale * vdir;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// nowhere near - move on
|
// nowhere near - move on
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1575,15 +1495,13 @@ std::vector<std::vector<IfcVector2>> GetContoursInPlane3D(const std::shared_ptr<
|
||||||
logSegment(s);
|
logSegment(s);
|
||||||
lineSegments.push_back(s);
|
lineSegments.push_back(s);
|
||||||
// next firstpoint should be this one
|
// next firstpoint should be this one
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// store the first intersection point
|
// store the first intersection point
|
||||||
firstPoint.x = intersection.x;
|
firstPoint.x = intersection.x;
|
||||||
firstPoint.y = intersection.y;
|
firstPoint.y = intersection.y;
|
||||||
gotFirstPoint = true;
|
gotFirstPoint = true;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// now got the second point, so store the pair
|
// now got the second point, so store the pair
|
||||||
IfcVector2 secondPoint(intersection.x,intersection.y);
|
IfcVector2 secondPoint(intersection.x,intersection.y);
|
||||||
auto s = std::pair<IfcVector2,IfcVector2>(firstPoint,secondPoint);
|
auto s = std::pair<IfcVector2,IfcVector2>(firstPoint,secondPoint);
|
||||||
|
@ -1691,29 +1609,28 @@ std::vector<std::vector<IfcVector2>> GetContoursInPlane3D(const std::shared_ptr<
|
||||||
return contours;
|
return contours;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::vector<IfcVector2>> GetContoursInPlane(const std::shared_ptr<TempMesh>& mesh,IfcMatrix3 planeSpace,
|
std::vector<std::vector<IfcVector2>> GetContoursInPlane(const std::shared_ptr<TempMesh>& mesh,
|
||||||
IfcVector3 planeNor,IfcFloat planeOffset,
|
IfcMatrix3 planeSpace,
|
||||||
IfcVector3 extrusionDir,IfcVector3& wall_extrusion,bool& first) {
|
IfcVector3 planeNor,
|
||||||
|
IfcFloat planeOffset,
|
||||||
if(mesh->mVertcnt.size() == 1)
|
IfcVector3 extrusionDir,
|
||||||
{
|
IfcVector3& wall_extrusion,
|
||||||
|
bool& first) {
|
||||||
|
if(mesh->mVertcnt.size() == 1) {
|
||||||
bool ok;
|
bool ok;
|
||||||
auto contour = GetContourInPlane2D(mesh,planeSpace,planeNor,planeOffset,extrusionDir,wall_extrusion,first,ok);
|
auto contour = GetContourInPlane2D(mesh,planeSpace,planeNor,planeOffset,extrusionDir,wall_extrusion,first,ok);
|
||||||
if(ok)
|
if(ok)
|
||||||
return std::vector<std::vector<IfcVector2>> {std::move(contour)};
|
return std::vector<std::vector<IfcVector2>> {std::move(contour)};
|
||||||
else
|
else
|
||||||
return std::vector<std::vector<IfcVector2>> {};
|
return std::vector<std::vector<IfcVector2>> {};
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
return GetContoursInPlane3D(mesh,planeSpace,planeOffset);
|
return GetContoursInPlane3D(mesh,planeSpace,planeOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,
|
bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,
|
||||||
TempMesh& curmesh)
|
TempMesh& curmesh) {
|
||||||
{
|
|
||||||
IFCImporter::LogWarn("forced to use poly2tri fallback method to generate wall openings");
|
IFCImporter::LogWarn("forced to use poly2tri fallback method to generate wall openings");
|
||||||
std::vector<IfcVector3>& out = curmesh.mVerts;
|
std::vector<IfcVector3>& out = curmesh.mVerts;
|
||||||
|
|
||||||
|
@ -1746,14 +1663,6 @@ bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,
|
||||||
// keep Z offset in the plane coordinate system. Ignoring precision issues
|
// keep Z offset in the plane coordinate system. Ignoring precision issues
|
||||||
// (which are present, of course), this should be the same value for
|
// (which are present, of course), this should be the same value for
|
||||||
// all polygon vertices (assuming the polygon is planar).
|
// all polygon vertices (assuming the polygon is planar).
|
||||||
|
|
||||||
|
|
||||||
// XXX this should be guarded, but we somehow need to pick a suitable
|
|
||||||
// epsilon
|
|
||||||
// if(coord != -1.0f) {
|
|
||||||
// assert(std::fabs(coord - vv.z) < 1e-3f);
|
|
||||||
// }
|
|
||||||
|
|
||||||
coord = vv.z;
|
coord = vv.z;
|
||||||
|
|
||||||
vmin = std::min(IfcVector2(vv.x, vv.y), vmin);
|
vmin = std::min(IfcVector2(vv.x, vv.y), vmin);
|
||||||
|
@ -1771,15 +1680,13 @@ bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,
|
||||||
// If this happens then the projection must have been wrong.
|
// If this happens then the projection must have been wrong.
|
||||||
ai_assert(vmax.Length());
|
ai_assert(vmax.Length());
|
||||||
|
|
||||||
ClipperLib::ExPolygons clipped;
|
ClipperLib::Paths clipped;
|
||||||
ClipperLib::Polygons holes_union;
|
ClipperLib::Paths holes_union;
|
||||||
|
|
||||||
|
|
||||||
IfcVector3 wall_extrusion;
|
IfcVector3 wall_extrusion;
|
||||||
bool first = true;
|
bool first = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
ClipperLib::Clipper clipper_holes;
|
ClipperLib::Clipper clipper_holes;
|
||||||
|
|
||||||
for(const TempOpening& t : openings) {
|
for(const TempOpening& t : openings) {
|
||||||
|
@ -1787,7 +1694,7 @@ bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,
|
||||||
|
|
||||||
for(auto& contour : contours) {
|
for(auto& contour : contours) {
|
||||||
// scale to clipping space
|
// scale to clipping space
|
||||||
ClipperLib::Polygon hole;
|
ClipperLib::Path hole;
|
||||||
for(IfcVector2& pip : contour) {
|
for(IfcVector2& pip : contour) {
|
||||||
pip.x = (pip.x - vmin.x) / vmax.x;
|
pip.x = (pip.x - vmin.x) / vmax.x;
|
||||||
pip.y = (pip.y - vmin.y) / vmax.y;
|
pip.y = (pip.y - vmin.y) / vmax.y;
|
||||||
|
@ -1797,16 +1704,9 @@ bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,
|
||||||
|
|
||||||
if(!ClipperLib::Orientation(hole)) {
|
if(!ClipperLib::Orientation(hole)) {
|
||||||
std::reverse(hole.begin(),hole.end());
|
std::reverse(hole.begin(),hole.end());
|
||||||
// assert(ClipperLib::Orientation(hole));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*ClipperLib::Polygons pol_temp(1), pol_temp2(1);
|
clipper_holes.AddPath(hole,ClipperLib::ptSubject, true);
|
||||||
pol_temp[0] = hole;
|
|
||||||
|
|
||||||
ClipperLib::OffsetPolygons(pol_temp,pol_temp2,5.0);
|
|
||||||
hole = pol_temp2[0];*/
|
|
||||||
|
|
||||||
clipper_holes.AddPolygon(hole,ClipperLib::ptSubject);
|
|
||||||
{
|
{
|
||||||
std::stringstream msg;
|
std::stringstream msg;
|
||||||
msg << "- added polygon ";
|
msg << "- added polygon ";
|
||||||
|
@ -1829,7 +1729,7 @@ bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,
|
||||||
// Now that we have the big union of all holes, subtract it from the outer contour
|
// Now that we have the big union of all holes, subtract it from the outer contour
|
||||||
// to obtain the final polygon to feed into the triangulator.
|
// to obtain the final polygon to feed into the triangulator.
|
||||||
{
|
{
|
||||||
ClipperLib::Polygon poly;
|
ClipperLib::Path poly;
|
||||||
for(IfcVector2& pip : contour_flat) {
|
for(IfcVector2& pip : contour_flat) {
|
||||||
pip.x = (pip.x - vmin.x) / vmax.x;
|
pip.x = (pip.x - vmin.x) / vmax.x;
|
||||||
pip.y = (pip.y - vmin.y) / vmax.y;
|
pip.y = (pip.y - vmin.y) / vmax.y;
|
||||||
|
@ -1841,16 +1741,14 @@ bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,
|
||||||
std::reverse(poly.begin(), poly.end());
|
std::reverse(poly.begin(), poly.end());
|
||||||
}
|
}
|
||||||
clipper_holes.Clear();
|
clipper_holes.Clear();
|
||||||
clipper_holes.AddPolygon(poly,ClipperLib::ptSubject);
|
clipper_holes.AddPath(poly,ClipperLib::ptSubject, true);
|
||||||
|
|
||||||
clipper_holes.AddPolygons(holes_union,ClipperLib::ptClip);
|
clipper_holes.AddPaths(holes_union,ClipperLib::ptClip, true);
|
||||||
clipper_holes.Execute(ClipperLib::ctDifference,clipped,
|
clipper_holes.Execute(ClipperLib::ctDifference,clipped,
|
||||||
ClipperLib::pftNonZero,
|
ClipperLib::pftNonZero,
|
||||||
ClipperLib::pftNonZero);
|
ClipperLib::pftNonZero);
|
||||||
}
|
}
|
||||||
|
} catch (const char* sx) {
|
||||||
}
|
|
||||||
catch (const char* sx) {
|
|
||||||
IFCImporter::LogError("Ifc: error during polygon clipping, skipping openings for this face: (Clipper: "
|
IFCImporter::LogError("Ifc: error during polygon clipping, skipping openings for this face: (Clipper: "
|
||||||
+ std::string(sx) + ")");
|
+ std::string(sx) + ")");
|
||||||
|
|
||||||
|
@ -1864,13 +1762,13 @@ bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,
|
||||||
old_vertcnt.swap(curmesh.mVertcnt);
|
old_vertcnt.swap(curmesh.mVertcnt);
|
||||||
|
|
||||||
std::vector< std::vector<p2t::Point*> > contours;
|
std::vector< std::vector<p2t::Point*> > contours;
|
||||||
for(ClipperLib::ExPolygon& clip : clipped) {
|
for(ClipperLib::Path &clip : clipped) {
|
||||||
|
|
||||||
contours.clear();
|
contours.clear();
|
||||||
|
|
||||||
// Build the outer polygon contour line for feeding into poly2tri
|
// Build the outer polygon contour line for feeding into poly2tri
|
||||||
std::vector<p2t::Point*> contour_points;
|
std::vector<p2t::Point*> contour_points;
|
||||||
for(ClipperLib::IntPoint& point : clip.outer) {
|
for(ClipperLib::IntPoint& point : clip) {
|
||||||
contour_points.push_back( new p2t::Point(from_int64(point.X), from_int64(point.Y)) );
|
contour_points.push_back( new p2t::Point(from_int64(point.X), from_int64(point.Y)) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1881,16 +1779,14 @@ bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,
|
||||||
// happen in production use if the input data is broken. An assertion would be
|
// happen in production use if the input data is broken. An assertion would be
|
||||||
// inappropriate.
|
// inappropriate.
|
||||||
cdt = new p2t::CDT(contour_points);
|
cdt = new p2t::CDT(contour_points);
|
||||||
}
|
} catch(const std::exception& e) {
|
||||||
catch(const std::exception& e) {
|
|
||||||
IFCImporter::LogError("Ifc: error during polygon triangulation, skipping some openings: (poly2tri: "
|
IFCImporter::LogError("Ifc: error during polygon triangulation, skipping some openings: (poly2tri: "
|
||||||
+ std::string(e.what()) + ")");
|
+ std::string(e.what()) + ")");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Build the poly2tri inner contours for all holes we got from ClipperLib
|
// Build the poly2tri inner contours for all holes we got from ClipperLib
|
||||||
for(ClipperLib::Polygon& opening : clip.holes) {
|
for(ClipperLib::Path& opening : holes_union) {
|
||||||
|
|
||||||
contours.emplace_back();
|
contours.emplace_back();
|
||||||
std::vector<p2t::Point*>& contour = contours.back();
|
std::vector<p2t::Point*>& contour = contours.back();
|
||||||
|
@ -1905,8 +1801,7 @@ bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,
|
||||||
try {
|
try {
|
||||||
// Note: See above
|
// Note: See above
|
||||||
cdt->Triangulate();
|
cdt->Triangulate();
|
||||||
}
|
} catch(const std::exception& e) {
|
||||||
catch(const std::exception& e) {
|
|
||||||
IFCImporter::LogError("Ifc: error during polygon triangulation, skipping some openings: (poly2tri: "
|
IFCImporter::LogError("Ifc: error during polygon triangulation, skipping some openings: (poly2tri: "
|
||||||
+ std::string(e.what()) + ")");
|
+ std::string(e.what()) + ")");
|
||||||
continue;
|
continue;
|
||||||
|
@ -1945,7 +1840,6 @@ bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} // ! IFC
|
} // ! IFC
|
||||||
} // ! Assimp
|
} // ! Assimp
|
||||||
|
|
||||||
|
@ -1953,4 +1847,4 @@ bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,
|
||||||
#undef from_int64
|
#undef from_int64
|
||||||
#undef one_vec
|
#undef one_vec
|
||||||
|
|
||||||
#endif
|
#endif // ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||||
|
|
|
@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
|
||||||
|
|
||||||
Copyright (c) 2006-2022, assimp team
|
Copyright (c) 2006-2022, assimp team
|
||||||
|
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use of this software in source and binary forms,
|
Redistribution and use of this software in source and binary forms,
|
||||||
|
@ -40,9 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @file IFCProfile.cpp
|
/// @file IFCProfile.cpp
|
||||||
* @brief Read profile and curves entities from IFC files
|
/// @brief Read profile and curves entities from IFC files
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||||
|
|
||||||
|
@ -52,8 +50,9 @@ namespace Assimp {
|
||||||
namespace IFC {
|
namespace IFC {
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ProcessPolyLine(const Schema_2x3::IfcPolyline& def, TempMesh& meshout, ConversionData& /*conv*/)
|
void ProcessPolyLine(const Schema_2x3::IfcPolyline& def,
|
||||||
{
|
TempMesh& meshout,
|
||||||
|
ConversionData& /*conv*/) {
|
||||||
// this won't produce a valid mesh, it just spits out a list of vertices
|
// this won't produce a valid mesh, it just spits out a list of vertices
|
||||||
IfcVector3 t;
|
IfcVector3 t;
|
||||||
for(const Schema_2x3::IfcCartesianPoint& cp : def.Points) {
|
for(const Schema_2x3::IfcCartesianPoint& cp : def.Points) {
|
||||||
|
@ -64,8 +63,9 @@ void ProcessPolyLine(const Schema_2x3::IfcPolyline& def, TempMesh& meshout, Conv
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
bool ProcessCurve(const Schema_2x3::IfcCurve& curve, TempMesh& meshout, ConversionData& conv)
|
bool ProcessCurve(const Schema_2x3::IfcCurve& curve,
|
||||||
{
|
TempMesh& meshout,
|
||||||
|
ConversionData& conv) {
|
||||||
std::unique_ptr<const Curve> cv(Curve::Convert(curve,conv));
|
std::unique_ptr<const Curve> cv(Curve::Convert(curve,conv));
|
||||||
if (!cv) {
|
if (!cv) {
|
||||||
IFCImporter::LogWarn("skipping unknown IfcCurve entity, type is ", curve.GetClassName());
|
IFCImporter::LogWarn("skipping unknown IfcCurve entity, type is ", curve.GetClassName());
|
||||||
|
@ -90,20 +90,23 @@ bool ProcessCurve(const Schema_2x3::IfcCurve& curve, TempMesh& meshout, Convers
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ProcessClosedProfile(const Schema_2x3::IfcArbitraryClosedProfileDef& def, TempMesh& meshout, ConversionData& conv)
|
void ProcessClosedProfile(const Schema_2x3::IfcArbitraryClosedProfileDef& def,
|
||||||
{
|
TempMesh& meshout,
|
||||||
|
ConversionData& conv) {
|
||||||
ProcessCurve(def.OuterCurve,meshout,conv);
|
ProcessCurve(def.OuterCurve,meshout,conv);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ProcessOpenProfile(const Schema_2x3::IfcArbitraryOpenProfileDef& def, TempMesh& meshout, ConversionData& conv)
|
void ProcessOpenProfile(const Schema_2x3::IfcArbitraryOpenProfileDef& def,
|
||||||
{
|
TempMesh& meshout,
|
||||||
|
ConversionData& conv) {
|
||||||
ProcessCurve(def.Curve,meshout,conv);
|
ProcessCurve(def.Curve,meshout,conv);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& def, TempMesh& meshout, ConversionData& conv)
|
void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& def,
|
||||||
{
|
TempMesh& meshout,
|
||||||
|
ConversionData& conv) {
|
||||||
if(const Schema_2x3::IfcRectangleProfileDef* const cprofile = def.ToPtr<Schema_2x3::IfcRectangleProfileDef>()) {
|
if(const Schema_2x3::IfcRectangleProfileDef* const cprofile = def.ToPtr<Schema_2x3::IfcRectangleProfileDef>()) {
|
||||||
const IfcFloat x = cprofile->XDim*0.5f, y = cprofile->YDim*0.5f;
|
const IfcFloat x = cprofile->XDim*0.5f, y = cprofile->YDim*0.5f;
|
||||||
|
|
||||||
|
@ -113,8 +116,7 @@ void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& de
|
||||||
meshout.mVerts.emplace_back(-x,-y, 0.f );
|
meshout.mVerts.emplace_back(-x,-y, 0.f );
|
||||||
meshout.mVerts.emplace_back( x,-y, 0.f );
|
meshout.mVerts.emplace_back( x,-y, 0.f );
|
||||||
meshout.mVertcnt.push_back(4);
|
meshout.mVertcnt.push_back(4);
|
||||||
}
|
} else if( const Schema_2x3::IfcCircleProfileDef* const circle = def.ToPtr<Schema_2x3::IfcCircleProfileDef>()) {
|
||||||
else if( const Schema_2x3::IfcCircleProfileDef* const circle = def.ToPtr<Schema_2x3::IfcCircleProfileDef>()) {
|
|
||||||
if(def.ToPtr<Schema_2x3::IfcCircleHollowProfileDef>()) {
|
if(def.ToPtr<Schema_2x3::IfcCircleHollowProfileDef>()) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
@ -129,8 +131,7 @@ void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& de
|
||||||
}
|
}
|
||||||
|
|
||||||
meshout.mVertcnt.push_back(static_cast<unsigned int>(segments));
|
meshout.mVertcnt.push_back(static_cast<unsigned int>(segments));
|
||||||
}
|
} else if( const Schema_2x3::IfcIShapeProfileDef* const ishape = def.ToPtr<Schema_2x3::IfcIShapeProfileDef>()) {
|
||||||
else if( const Schema_2x3::IfcIShapeProfileDef* const ishape = def.ToPtr<Schema_2x3::IfcIShapeProfileDef>()) {
|
|
||||||
// construct simplified IBeam shape
|
// construct simplified IBeam shape
|
||||||
const IfcFloat offset = (ishape->OverallWidth - ishape->WebThickness) / 2;
|
const IfcFloat offset = (ishape->OverallWidth - ishape->WebThickness) / 2;
|
||||||
const IfcFloat inner_height = ishape->OverallDepth - ishape->FlangeThickness * 2;
|
const IfcFloat inner_height = ishape->OverallDepth - ishape->FlangeThickness * 2;
|
||||||
|
@ -150,8 +151,7 @@ void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& de
|
||||||
meshout.mVerts.emplace_back(ishape->OverallWidth,0,0);
|
meshout.mVerts.emplace_back(ishape->OverallWidth,0,0);
|
||||||
|
|
||||||
meshout.mVertcnt.push_back(12);
|
meshout.mVertcnt.push_back(12);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
IFCImporter::LogWarn("skipping unknown IfcParameterizedProfileDef entity, type is ", def.GetClassName());
|
IFCImporter::LogWarn("skipping unknown IfcParameterizedProfileDef entity, type is ", def.GetClassName());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -162,18 +162,14 @@ void ProcessParametrizedProfile(const Schema_2x3::IfcParameterizedProfileDef& de
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
bool ProcessProfile(const Schema_2x3::IfcProfileDef& prof, TempMesh& meshout, ConversionData& conv)
|
bool ProcessProfile(const Schema_2x3::IfcProfileDef& prof, TempMesh& meshout, ConversionData& conv) {
|
||||||
{
|
|
||||||
if(const Schema_2x3::IfcArbitraryClosedProfileDef* const cprofile = prof.ToPtr<Schema_2x3::IfcArbitraryClosedProfileDef>()) {
|
if(const Schema_2x3::IfcArbitraryClosedProfileDef* const cprofile = prof.ToPtr<Schema_2x3::IfcArbitraryClosedProfileDef>()) {
|
||||||
ProcessClosedProfile(*cprofile,meshout,conv);
|
ProcessClosedProfile(*cprofile,meshout,conv);
|
||||||
}
|
} else if(const Schema_2x3::IfcArbitraryOpenProfileDef* const copen = prof.ToPtr<Schema_2x3::IfcArbitraryOpenProfileDef>()) {
|
||||||
else if(const Schema_2x3::IfcArbitraryOpenProfileDef* const copen = prof.ToPtr<Schema_2x3::IfcArbitraryOpenProfileDef>()) {
|
|
||||||
ProcessOpenProfile(*copen,meshout,conv);
|
ProcessOpenProfile(*copen,meshout,conv);
|
||||||
}
|
} else if(const Schema_2x3::IfcParameterizedProfileDef* const cparam = prof.ToPtr<Schema_2x3::IfcParameterizedProfileDef>()) {
|
||||||
else if(const Schema_2x3::IfcParameterizedProfileDef* const cparam = prof.ToPtr<Schema_2x3::IfcParameterizedProfileDef>()) {
|
|
||||||
ProcessParametrizedProfile(*cparam,meshout,conv);
|
ProcessParametrizedProfile(*cparam,meshout,conv);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
IFCImporter::LogWarn("skipping unknown IfcProfileDef entity, type is ", prof.GetClassName());
|
IFCImporter::LogWarn("skipping unknown IfcProfileDef entity, type is ", prof.GetClassName());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ Open Asset Import Library (assimp)
|
||||||
|
|
||||||
Copyright (c) 2006-2022, assimp team
|
Copyright (c) 2006-2022, assimp team
|
||||||
|
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use of this software in source and binary forms,
|
Redistribution and use of this software in source and binary forms,
|
||||||
|
@ -40,9 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @file IFCUtil.cpp
|
/// @file IFCUtil.cpp
|
||||||
* @brief Implementation of conversion routines for some common Ifc helper entities.
|
/// @brief Implementation of conversion routines for some common Ifc helper entities.
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||||
|
|
||||||
|
@ -66,8 +64,7 @@ void TempOpening::Transform(const IfcMatrix4& mat) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
aiMesh* TempMesh::ToMesh()
|
aiMesh* TempMesh::ToMesh() {
|
||||||
{
|
|
||||||
ai_assert(mVerts.size() == std::accumulate(mVertcnt.begin(),mVertcnt.end(),size_t(0)));
|
ai_assert(mVerts.size() == std::accumulate(mVertcnt.begin(),mVertcnt.end(),size_t(0)));
|
||||||
|
|
||||||
if (mVerts.empty()) {
|
if (mVerts.empty()) {
|
||||||
|
@ -105,36 +102,31 @@ aiMesh* TempMesh::ToMesh()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void TempMesh::Clear()
|
void TempMesh::Clear() {
|
||||||
{
|
|
||||||
mVerts.clear();
|
mVerts.clear();
|
||||||
mVertcnt.clear();
|
mVertcnt.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void TempMesh::Transform(const IfcMatrix4& mat)
|
void TempMesh::Transform(const IfcMatrix4& mat) {
|
||||||
{
|
|
||||||
for(IfcVector3& v : mVerts) {
|
for(IfcVector3& v : mVerts) {
|
||||||
v *= mat;
|
v *= mat;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
IfcVector3 TempMesh::Center() const
|
IfcVector3 TempMesh::Center() const {
|
||||||
{
|
return mVerts.empty() ? IfcVector3(0.0f, 0.0f, 0.0f) : (std::accumulate(mVerts.begin(),mVerts.end(),IfcVector3()) / static_cast<IfcFloat>(mVerts.size()));
|
||||||
return (mVerts.size() == 0) ? IfcVector3(0.0f, 0.0f, 0.0f) : (std::accumulate(mVerts.begin(),mVerts.end(),IfcVector3()) / static_cast<IfcFloat>(mVerts.size()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void TempMesh::Append(const TempMesh& other)
|
void TempMesh::Append(const TempMesh& other) {
|
||||||
{
|
|
||||||
mVerts.insert(mVerts.end(),other.mVerts.begin(),other.mVerts.end());
|
mVerts.insert(mVerts.end(),other.mVerts.begin(),other.mVerts.end());
|
||||||
mVertcnt.insert(mVertcnt.end(),other.mVertcnt.begin(),other.mVertcnt.end());
|
mVertcnt.insert(mVertcnt.end(),other.mVertcnt.begin(),other.mVertcnt.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void TempMesh::RemoveDegenerates()
|
void TempMesh::RemoveDegenerates() {
|
||||||
{
|
|
||||||
// The strategy is simple: walk the mesh and compute normals using
|
// The strategy is simple: walk the mesh and compute normals using
|
||||||
// Newell's algorithm. The length of the normals gives the area
|
// Newell's algorithm. The length of the normals gives the area
|
||||||
// of the polygons, which is close to zero for lines.
|
// of the polygons, which is close to zero for lines.
|
||||||
|
@ -167,11 +159,9 @@ void TempMesh::RemoveDegenerates()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
IfcVector3 TempMesh::ComputePolygonNormal(const IfcVector3* vtcs, size_t cnt, bool normalize)
|
IfcVector3 TempMesh::ComputePolygonNormal(const IfcVector3* vtcs, size_t cnt, bool normalize) {
|
||||||
{
|
|
||||||
std::vector<IfcFloat> temp((cnt+2)*3);
|
std::vector<IfcFloat> temp((cnt+2)*3);
|
||||||
for( size_t vofs = 0, i = 0; vofs < cnt; ++vofs )
|
for( size_t vofs = 0, i = 0; vofs < cnt; ++vofs ) {
|
||||||
{
|
|
||||||
const IfcVector3& v = vtcs[vofs];
|
const IfcVector3& v = vtcs[vofs];
|
||||||
temp[i++] = v.x;
|
temp[i++] = v.x;
|
||||||
temp[i++] = v.y;
|
temp[i++] = v.y;
|
||||||
|
@ -186,8 +176,7 @@ IfcVector3 TempMesh::ComputePolygonNormal(const IfcVector3* vtcs, size_t cnt, bo
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void TempMesh::ComputePolygonNormals(std::vector<IfcVector3>& normals,
|
void TempMesh::ComputePolygonNormals(std::vector<IfcVector3>& normals,
|
||||||
bool normalize,
|
bool normalize,
|
||||||
size_t ofs) const
|
size_t ofs) const {
|
||||||
{
|
|
||||||
size_t max_vcount = 0;
|
size_t max_vcount = 0;
|
||||||
std::vector<unsigned int>::const_iterator begin = mVertcnt.begin()+ofs, end = mVertcnt.end(), iit;
|
std::vector<unsigned int>::const_iterator begin = mVertcnt.begin()+ofs, end = mVertcnt.end(), iit;
|
||||||
for(iit = begin; iit != end; ++iit) {
|
for(iit = begin; iit != end; ++iit) {
|
||||||
|
@ -250,29 +239,27 @@ struct FindVector {
|
||||||
};
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void TempMesh::FixupFaceOrientation()
|
void TempMesh::FixupFaceOrientation() {
|
||||||
{
|
|
||||||
const IfcVector3 vavg = Center();
|
const IfcVector3 vavg = Center();
|
||||||
|
|
||||||
// create a list of start indices for all faces to allow random access to faces
|
// create a list of start indices for all faces to allow random access to faces
|
||||||
std::vector<size_t> faceStartIndices(mVertcnt.size());
|
std::vector<size_t> faceStartIndices(mVertcnt.size());
|
||||||
for( size_t i = 0, a = 0; a < mVertcnt.size(); i += mVertcnt[a], ++a )
|
for( size_t i = 0, a = 0; a < mVertcnt.size(); i += mVertcnt[a], ++a ) {
|
||||||
faceStartIndices[a] = i;
|
faceStartIndices[a] = i;
|
||||||
|
}
|
||||||
|
|
||||||
// list all faces on a vertex
|
// list all faces on a vertex
|
||||||
std::map<IfcVector3, std::vector<size_t>, CompareVector> facesByVertex;
|
std::map<IfcVector3, std::vector<size_t>, CompareVector> facesByVertex;
|
||||||
for( size_t a = 0; a < mVertcnt.size(); ++a )
|
for( size_t a = 0; a < mVertcnt.size(); ++a ) {
|
||||||
{
|
for( size_t b = 0; b < mVertcnt[a]; ++b ) {
|
||||||
for( size_t b = 0; b < mVertcnt[a]; ++b )
|
|
||||||
facesByVertex[mVerts[faceStartIndices[a] + b]].push_back(a);
|
facesByVertex[mVerts[faceStartIndices[a] + b]].push_back(a);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// determine neighbourhood for all polys
|
// determine neighbourhood for all polys
|
||||||
std::vector<size_t> neighbour(mVerts.size(), SIZE_MAX);
|
std::vector<size_t> neighbour(mVerts.size(), SIZE_MAX);
|
||||||
std::vector<size_t> tempIntersect(10);
|
std::vector<size_t> tempIntersect(10);
|
||||||
for( size_t a = 0; a < mVertcnt.size(); ++a )
|
for( size_t a = 0; a < mVertcnt.size(); ++a ) {
|
||||||
{
|
for( size_t b = 0; b < mVertcnt[a]; ++b ) {
|
||||||
for( size_t b = 0; b < mVertcnt[a]; ++b )
|
|
||||||
{
|
|
||||||
size_t ib = faceStartIndices[a] + b, nib = faceStartIndices[a] + (b + 1) % mVertcnt[a];
|
size_t ib = faceStartIndices[a] + b, nib = faceStartIndices[a] + (b + 1) % mVertcnt[a];
|
||||||
const std::vector<size_t>& facesOnB = facesByVertex[mVerts[ib]];
|
const std::vector<size_t>& facesOnB = facesByVertex[mVerts[ib]];
|
||||||
const std::vector<size_t>& facesOnNB = facesByVertex[mVerts[nib]];
|
const std::vector<size_t>& facesOnNB = facesByVertex[mVerts[nib]];
|
||||||
|
@ -281,10 +268,12 @@ void TempMesh::FixupFaceOrientation()
|
||||||
std::vector<size_t>::iterator sectend = std::set_intersection(
|
std::vector<size_t>::iterator sectend = std::set_intersection(
|
||||||
facesOnB.begin(), facesOnB.end(), facesOnNB.begin(), facesOnNB.end(), sectstart);
|
facesOnB.begin(), facesOnB.end(), facesOnNB.begin(), facesOnNB.end(), sectstart);
|
||||||
|
|
||||||
if( std::distance(sectstart, sectend) != 2 )
|
if( std::distance(sectstart, sectend) != 2 ) {
|
||||||
continue;
|
continue;
|
||||||
if( *sectstart == a )
|
}
|
||||||
|
if( *sectstart == a ) {
|
||||||
++sectstart;
|
++sectstart;
|
||||||
|
}
|
||||||
neighbour[ib] = *sectstart;
|
neighbour[ib] = *sectstart;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -293,15 +282,14 @@ void TempMesh::FixupFaceOrientation()
|
||||||
// facing outwards. So we reverse this face to point outwards in relation to the center. Then we adapt neighbouring
|
// facing outwards. So we reverse this face to point outwards in relation to the center. Then we adapt neighbouring
|
||||||
// faces to have the same winding until all faces have been tested.
|
// faces to have the same winding until all faces have been tested.
|
||||||
std::vector<bool> faceDone(mVertcnt.size(), false);
|
std::vector<bool> faceDone(mVertcnt.size(), false);
|
||||||
while( std::count(faceDone.begin(), faceDone.end(), false) != 0 )
|
while( std::count(faceDone.begin(), faceDone.end(), false) != 0 ) {
|
||||||
{
|
|
||||||
// find the farthest of the remaining faces
|
// find the farthest of the remaining faces
|
||||||
size_t farthestIndex = SIZE_MAX;
|
size_t farthestIndex = SIZE_MAX;
|
||||||
IfcFloat farthestDistance = -1.0;
|
IfcFloat farthestDistance = -1.0;
|
||||||
for( size_t a = 0; a < mVertcnt.size(); ++a )
|
for( size_t a = 0; a < mVertcnt.size(); ++a ) {
|
||||||
{
|
if( faceDone[a] ) {
|
||||||
if( faceDone[a] )
|
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
IfcVector3 faceCenter = std::accumulate(mVerts.begin() + faceStartIndices[a],
|
IfcVector3 faceCenter = std::accumulate(mVerts.begin() + faceStartIndices[a],
|
||||||
mVerts.begin() + faceStartIndices[a] + mVertcnt[a], IfcVector3(0.0)) / IfcFloat(mVertcnt[a]);
|
mVerts.begin() + faceStartIndices[a] + mVertcnt[a], IfcVector3(0.0)) / IfcFloat(mVertcnt[a]);
|
||||||
IfcFloat dst = (faceCenter - vavg).SquareLength();
|
IfcFloat dst = (faceCenter - vavg).SquareLength();
|
||||||
|
@ -315,8 +303,7 @@ void TempMesh::FixupFaceOrientation()
|
||||||
/ IfcFloat(mVertcnt[farthestIndex]);
|
/ IfcFloat(mVertcnt[farthestIndex]);
|
||||||
// We accept a bit of negative orientation without reversing. In case of doubt, prefer the orientation given in
|
// We accept a bit of negative orientation without reversing. In case of doubt, prefer the orientation given in
|
||||||
// the file.
|
// the file.
|
||||||
if( (farthestNormal * (farthestCenter - vavg).Normalize()) < -0.4 )
|
if( (farthestNormal * (farthestCenter - vavg).Normalize()) < -0.4 ) {
|
||||||
{
|
|
||||||
size_t fsi = faceStartIndices[farthestIndex], fvc = mVertcnt[farthestIndex];
|
size_t fsi = faceStartIndices[farthestIndex], fvc = mVertcnt[farthestIndex];
|
||||||
std::reverse(mVerts.begin() + fsi, mVerts.begin() + fsi + fvc);
|
std::reverse(mVerts.begin() + fsi, mVerts.begin() + fsi + fvc);
|
||||||
std::reverse(neighbour.begin() + fsi, neighbour.begin() + fsi + fvc);
|
std::reverse(neighbour.begin() + fsi, neighbour.begin() + fsi + fvc);
|
||||||
|
@ -333,19 +320,18 @@ void TempMesh::FixupFaceOrientation()
|
||||||
todo.push_back(farthestIndex);
|
todo.push_back(farthestIndex);
|
||||||
|
|
||||||
// go over its neighbour faces recursively and adapt their winding order to match the farthest face
|
// go over its neighbour faces recursively and adapt their winding order to match the farthest face
|
||||||
while( !todo.empty() )
|
while( !todo.empty() ) {
|
||||||
{
|
|
||||||
size_t tdf = todo.back();
|
size_t tdf = todo.back();
|
||||||
size_t vsi = faceStartIndices[tdf], vc = mVertcnt[tdf];
|
size_t vsi = faceStartIndices[tdf], vc = mVertcnt[tdf];
|
||||||
todo.pop_back();
|
todo.pop_back();
|
||||||
|
|
||||||
// check its neighbours
|
// check its neighbours
|
||||||
for( size_t a = 0; a < vc; ++a )
|
for( size_t a = 0; a < vc; ++a ) {
|
||||||
{
|
|
||||||
// ignore neighbours if we already checked them
|
// ignore neighbours if we already checked them
|
||||||
size_t nbi = neighbour[vsi + a];
|
size_t nbi = neighbour[vsi + a];
|
||||||
if( nbi == SIZE_MAX || faceDone[nbi] )
|
if( nbi == SIZE_MAX || faceDone[nbi] ) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const IfcVector3& vp = mVerts[vsi + a];
|
const IfcVector3& vp = mVerts[vsi + a];
|
||||||
size_t nbvsi = faceStartIndices[nbi], nbvc = mVertcnt[nbi];
|
size_t nbvsi = faceStartIndices[nbi], nbvc = mVertcnt[nbi];
|
||||||
|
@ -388,31 +374,7 @@ void TempMesh::RemoveAdjacentDuplicates() {
|
||||||
IfcVector3 vmin,vmax;
|
IfcVector3 vmin,vmax;
|
||||||
ArrayBounds(&*base, cnt ,vmin,vmax);
|
ArrayBounds(&*base, cnt ,vmin,vmax);
|
||||||
|
|
||||||
|
|
||||||
const IfcFloat epsilon = (vmax-vmin).SquareLength() / static_cast<IfcFloat>(1e9);
|
const IfcFloat epsilon = (vmax-vmin).SquareLength() / static_cast<IfcFloat>(1e9);
|
||||||
//const IfcFloat dotepsilon = 1e-9;
|
|
||||||
|
|
||||||
//// look for vertices that lie directly on the line between their predecessor and their
|
|
||||||
//// successor and replace them with either of them.
|
|
||||||
|
|
||||||
//for(size_t i = 0; i < cnt; ++i) {
|
|
||||||
// IfcVector3& v1 = *(base+i), &v0 = *(base+(i?i-1:cnt-1)), &v2 = *(base+(i+1)%cnt);
|
|
||||||
// const IfcVector3& d0 = (v1-v0), &d1 = (v2-v1);
|
|
||||||
// const IfcFloat l0 = d0.SquareLength(), l1 = d1.SquareLength();
|
|
||||||
// if (!l0 || !l1) {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const IfcFloat d = (d0/std::sqrt(l0))*(d1/std::sqrt(l1));
|
|
||||||
|
|
||||||
// if ( d >= 1.f-dotepsilon ) {
|
|
||||||
// v1 = v0;
|
|
||||||
// }
|
|
||||||
// else if ( d < -1.f+dotepsilon ) {
|
|
||||||
// v2 = v1;
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
// drop any identical, adjacent vertices. this pass will collect the dropouts
|
// drop any identical, adjacent vertices. this pass will collect the dropouts
|
||||||
// of the previous pass as a side-effect.
|
// of the previous pass as a side-effect.
|
||||||
|
@ -440,78 +402,58 @@ void TempMesh::RemoveAdjacentDuplicates() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void TempMesh::Swap(TempMesh& other)
|
void TempMesh::Swap(TempMesh& other) {
|
||||||
{
|
|
||||||
mVertcnt.swap(other.mVertcnt);
|
mVertcnt.swap(other.mVertcnt);
|
||||||
mVerts.swap(other.mVerts);
|
mVerts.swap(other.mVerts);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
bool IsTrue(const ::Assimp::STEP::EXPRESS::BOOLEAN& in)
|
bool IsTrue(const ::Assimp::STEP::EXPRESS::BOOLEAN& in) {
|
||||||
{
|
|
||||||
return (std::string)in == "TRUE" || (std::string)in == "T";
|
return (std::string)in == "TRUE" || (std::string)in == "T";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
IfcFloat ConvertSIPrefix(const std::string& prefix)
|
IfcFloat ConvertSIPrefix(const std::string& prefix) {
|
||||||
{
|
|
||||||
if (prefix == "EXA") {
|
if (prefix == "EXA") {
|
||||||
return 1e18f;
|
return 1e18f;
|
||||||
}
|
} else if (prefix == "PETA") {
|
||||||
else if (prefix == "PETA") {
|
|
||||||
return 1e15f;
|
return 1e15f;
|
||||||
}
|
} else if (prefix == "TERA") {
|
||||||
else if (prefix == "TERA") {
|
|
||||||
return 1e12f;
|
return 1e12f;
|
||||||
}
|
} else if (prefix == "GIGA") {
|
||||||
else if (prefix == "GIGA") {
|
|
||||||
return 1e9f;
|
return 1e9f;
|
||||||
}
|
} else if (prefix == "MEGA") {
|
||||||
else if (prefix == "MEGA") {
|
|
||||||
return 1e6f;
|
return 1e6f;
|
||||||
}
|
} else if (prefix == "KILO") {
|
||||||
else if (prefix == "KILO") {
|
|
||||||
return 1e3f;
|
return 1e3f;
|
||||||
}
|
} else if (prefix == "HECTO") {
|
||||||
else if (prefix == "HECTO") {
|
|
||||||
return 1e2f;
|
return 1e2f;
|
||||||
}
|
} else if (prefix == "DECA") {
|
||||||
else if (prefix == "DECA") {
|
|
||||||
return 1e-0f;
|
return 1e-0f;
|
||||||
}
|
} else if (prefix == "DECI") {
|
||||||
else if (prefix == "DECI") {
|
|
||||||
return 1e-1f;
|
return 1e-1f;
|
||||||
}
|
} else if (prefix == "CENTI") {
|
||||||
else if (prefix == "CENTI") {
|
|
||||||
return 1e-2f;
|
return 1e-2f;
|
||||||
}
|
} else if (prefix == "MILLI") {
|
||||||
else if (prefix == "MILLI") {
|
|
||||||
return 1e-3f;
|
return 1e-3f;
|
||||||
}
|
} else if (prefix == "MICRO") {
|
||||||
else if (prefix == "MICRO") {
|
|
||||||
return 1e-6f;
|
return 1e-6f;
|
||||||
}
|
} else if (prefix == "NANO") {
|
||||||
else if (prefix == "NANO") {
|
|
||||||
return 1e-9f;
|
return 1e-9f;
|
||||||
}
|
} else if (prefix == "PICO") {
|
||||||
else if (prefix == "PICO") {
|
|
||||||
return 1e-12f;
|
return 1e-12f;
|
||||||
}
|
} else if (prefix == "FEMTO") {
|
||||||
else if (prefix == "FEMTO") {
|
|
||||||
return 1e-15f;
|
return 1e-15f;
|
||||||
}
|
} else if (prefix == "ATTO") {
|
||||||
else if (prefix == "ATTO") {
|
|
||||||
return 1e-18f;
|
return 1e-18f;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
IFCImporter::LogError("Unrecognized SI prefix: ", prefix);
|
IFCImporter::LogError("Unrecognized SI prefix: ", prefix);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourRgb& in)
|
void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourRgb& in) {
|
||||||
{
|
|
||||||
out.r = static_cast<float>( in.Red );
|
out.r = static_cast<float>( in.Red );
|
||||||
out.g = static_cast<float>( in.Green );
|
out.g = static_cast<float>( in.Green );
|
||||||
out.b = static_cast<float>( in.Blue );
|
out.b = static_cast<float>( in.Blue );
|
||||||
|
@ -519,8 +461,10 @@ void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourRgb& in)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourOrFactor& in,ConversionData& conv,const aiColor4D* base)
|
void ConvertColor(aiColor4D& out,
|
||||||
{
|
const Schema_2x3::IfcColourOrFactor& in,
|
||||||
|
ConversionData& conv,
|
||||||
|
const aiColor4D* base) {
|
||||||
if (const ::Assimp::STEP::EXPRESS::REAL* const r = in.ToPtr<::Assimp::STEP::EXPRESS::REAL>()) {
|
if (const ::Assimp::STEP::EXPRESS::REAL* const r = in.ToPtr<::Assimp::STEP::EXPRESS::REAL>()) {
|
||||||
out.r = out.g = out.b = static_cast<float>(*r);
|
out.r = out.g = out.b = static_cast<float>(*r);
|
||||||
if(base) {
|
if(base) {
|
||||||
|
@ -528,20 +472,18 @@ void ConvertColor(aiColor4D& out, const Schema_2x3::IfcColourOrFactor& in,Conver
|
||||||
out.g *= static_cast<float>( base->g );
|
out.g *= static_cast<float>( base->g );
|
||||||
out.b *= static_cast<float>( base->b );
|
out.b *= static_cast<float>( base->b );
|
||||||
out.a = static_cast<float>( base->a );
|
out.a = static_cast<float>( base->a );
|
||||||
|
} else {
|
||||||
|
out.a = 1.0;
|
||||||
}
|
}
|
||||||
else out.a = 1.0;
|
} else if (const Schema_2x3::IfcColourRgb* const rgb = in.ResolveSelectPtr<Schema_2x3::IfcColourRgb>(conv.db)) {
|
||||||
}
|
|
||||||
else if (const Schema_2x3::IfcColourRgb* const rgb = in.ResolveSelectPtr<Schema_2x3::IfcColourRgb>(conv.db)) {
|
|
||||||
ConvertColor(out,*rgb);
|
ConvertColor(out,*rgb);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
IFCImporter::LogWarn("skipping unknown IfcColourOrFactor entity");
|
IFCImporter::LogWarn("skipping unknown IfcColourOrFactor entity");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ConvertCartesianPoint(IfcVector3& out, const Schema_2x3::IfcCartesianPoint& in)
|
void ConvertCartesianPoint(IfcVector3& out, const Schema_2x3::IfcCartesianPoint& in) {
|
||||||
{
|
|
||||||
out = IfcVector3();
|
out = IfcVector3();
|
||||||
for(size_t i = 0; i < in.Coordinates.size(); ++i) {
|
for(size_t i = 0; i < in.Coordinates.size(); ++i) {
|
||||||
out[static_cast<unsigned int>(i)] = in.Coordinates[i];
|
out[static_cast<unsigned int>(i)] = in.Coordinates[i];
|
||||||
|
@ -549,15 +491,13 @@ void ConvertCartesianPoint(IfcVector3& out, const Schema_2x3::IfcCartesianPoint&
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ConvertVector(IfcVector3& out, const Schema_2x3::IfcVector& in)
|
void ConvertVector(IfcVector3& out, const Schema_2x3::IfcVector& in) {
|
||||||
{
|
|
||||||
ConvertDirection(out,in.Orientation);
|
ConvertDirection(out,in.Orientation);
|
||||||
out *= in.Magnitude;
|
out *= in.Magnitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ConvertDirection(IfcVector3& out, const Schema_2x3::IfcDirection& in)
|
void ConvertDirection(IfcVector3& out, const Schema_2x3::IfcDirection& in) {
|
||||||
{
|
|
||||||
out = IfcVector3();
|
out = IfcVector3();
|
||||||
for(size_t i = 0; i < in.DirectionRatios.size(); ++i) {
|
for(size_t i = 0; i < in.DirectionRatios.size(); ++i) {
|
||||||
out[static_cast<unsigned int>(i)] = in.DirectionRatios[i];
|
out[static_cast<unsigned int>(i)] = in.DirectionRatios[i];
|
||||||
|
@ -571,8 +511,7 @@ void ConvertDirection(IfcVector3& out, const Schema_2x3::IfcDirection& in)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void AssignMatrixAxes(IfcMatrix4& out, const IfcVector3& x, const IfcVector3& y, const IfcVector3& z)
|
void AssignMatrixAxes(IfcMatrix4& out, const IfcVector3& x, const IfcVector3& y, const IfcVector3& z) {
|
||||||
{
|
|
||||||
out.a1 = x.x;
|
out.a1 = x.x;
|
||||||
out.b1 = x.y;
|
out.b1 = x.y;
|
||||||
out.c1 = x.z;
|
out.c1 = x.z;
|
||||||
|
@ -587,8 +526,7 @@ void AssignMatrixAxes(IfcMatrix4& out, const IfcVector3& x, const IfcVector3& y,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement3D& in)
|
void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement3D& in) {
|
||||||
{
|
|
||||||
IfcVector3 loc;
|
IfcVector3 loc;
|
||||||
ConvertCartesianPoint(loc,in.Location);
|
ConvertCartesianPoint(loc,in.Location);
|
||||||
|
|
||||||
|
@ -612,8 +550,7 @@ void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement3D
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement2D& in)
|
void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement2D& in) {
|
||||||
{
|
|
||||||
IfcVector3 loc;
|
IfcVector3 loc;
|
||||||
ConvertCartesianPoint(loc,in.Location);
|
ConvertCartesianPoint(loc,in.Location);
|
||||||
|
|
||||||
|
@ -629,34 +566,28 @@ void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement2D
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ConvertAxisPlacement(IfcVector3& axis, IfcVector3& pos, const Schema_2x3::IfcAxis1Placement& in)
|
void ConvertAxisPlacement(IfcVector3& axis, IfcVector3& pos, const Schema_2x3::IfcAxis1Placement& in) {
|
||||||
{
|
|
||||||
ConvertCartesianPoint(pos,in.Location);
|
ConvertCartesianPoint(pos,in.Location);
|
||||||
if (in.Axis) {
|
if (in.Axis) {
|
||||||
ConvertDirection(axis,in.Axis.Get());
|
ConvertDirection(axis,in.Axis.Get());
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
axis = IfcVector3(0.f,0.f,1.f);
|
axis = IfcVector3(0.f,0.f,1.f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement& in, ConversionData& conv)
|
void ConvertAxisPlacement(IfcMatrix4& out, const Schema_2x3::IfcAxis2Placement& in, ConversionData& conv) {
|
||||||
{
|
|
||||||
if(const Schema_2x3::IfcAxis2Placement3D* pl3 = in.ResolveSelectPtr<Schema_2x3::IfcAxis2Placement3D>(conv.db)) {
|
if(const Schema_2x3::IfcAxis2Placement3D* pl3 = in.ResolveSelectPtr<Schema_2x3::IfcAxis2Placement3D>(conv.db)) {
|
||||||
ConvertAxisPlacement(out,*pl3);
|
ConvertAxisPlacement(out,*pl3);
|
||||||
}
|
} else if(const Schema_2x3::IfcAxis2Placement2D* pl2 = in.ResolveSelectPtr<Schema_2x3::IfcAxis2Placement2D>(conv.db)) {
|
||||||
else if(const Schema_2x3::IfcAxis2Placement2D* pl2 = in.ResolveSelectPtr<Schema_2x3::IfcAxis2Placement2D>(conv.db)) {
|
|
||||||
ConvertAxisPlacement(out,*pl2);
|
ConvertAxisPlacement(out,*pl2);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
IFCImporter::LogWarn("skipping unknown IfcAxis2Placement entity");
|
IFCImporter::LogWarn("skipping unknown IfcAxis2Placement entity");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTransformationOperator& op)
|
void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTransformationOperator& op) {
|
||||||
{
|
|
||||||
IfcVector3 loc;
|
IfcVector3 loc;
|
||||||
ConvertCartesianPoint(loc,op.LocalOrigin);
|
ConvertCartesianPoint(loc,op.LocalOrigin);
|
||||||
|
|
||||||
|
@ -677,14 +608,12 @@ void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTra
|
||||||
IfcMatrix4::Translation(loc,locm);
|
IfcMatrix4::Translation(loc,locm);
|
||||||
AssignMatrixAxes(out,x,y,z);
|
AssignMatrixAxes(out,x,y,z);
|
||||||
|
|
||||||
|
|
||||||
IfcVector3 vscale;
|
IfcVector3 vscale;
|
||||||
if (const Schema_2x3::IfcCartesianTransformationOperator3DnonUniform* nuni = op.ToPtr<Schema_2x3::IfcCartesianTransformationOperator3DnonUniform>()) {
|
if (const Schema_2x3::IfcCartesianTransformationOperator3DnonUniform* nuni = op.ToPtr<Schema_2x3::IfcCartesianTransformationOperator3DnonUniform>()) {
|
||||||
vscale.x = nuni->Scale?op.Scale.Get():1.f;
|
vscale.x = nuni->Scale?op.Scale.Get():1.f;
|
||||||
vscale.y = nuni->Scale2?nuni->Scale2.Get():1.f;
|
vscale.y = nuni->Scale2?nuni->Scale2.Get():1.f;
|
||||||
vscale.z = nuni->Scale3?nuni->Scale3.Get():1.f;
|
vscale.z = nuni->Scale3?nuni->Scale3.Get():1.f;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
const IfcFloat sc = op.Scale?op.Scale.Get():1.f;
|
const IfcFloat sc = op.Scale?op.Scale.Get():1.f;
|
||||||
vscale = IfcVector3(sc,sc,sc);
|
vscale = IfcVector3(sc,sc,sc);
|
||||||
}
|
}
|
||||||
|
@ -695,8 +624,7 @@ void ConvertTransformOperator(IfcMatrix4& out, const Schema_2x3::IfcCartesianTra
|
||||||
out = locm * out * s;
|
out = locm * out * s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} // ! IFC
|
} // ! IFC
|
||||||
} // ! Assimp
|
} // ! Assimp
|
||||||
|
|
||||||
#endif
|
#endif // ASSIMP_BUILD_NO_IFC_IMPORTER
|
||||||
|
|
|
@ -575,8 +575,8 @@ void SetupMapping(aiMaterial *mat, aiTextureMapping mode, const aiVector3D &axis
|
||||||
m->mSemantic = prop->mSemantic;
|
m->mSemantic = prop->mSemantic;
|
||||||
m->mType = aiPTI_Float;
|
m->mType = aiPTI_Float;
|
||||||
|
|
||||||
m->mDataLength = 12;
|
m->mDataLength = sizeof(aiVector3D);
|
||||||
m->mData = new char[12];
|
m->mData = new char[m->mDataLength];
|
||||||
*((aiVector3D *)m->mData) = axis;
|
*((aiVector3D *)m->mData) = axis;
|
||||||
p.push_back(m);
|
p.push_back(m);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
Open Asset Import Library (assimp)
|
Open Asset Import Library (assimp)
|
||||||
---------------------------------------------------------------------------
|
---------------------------------------------------------------------------
|
||||||
|
|
||||||
Copyright (c) 2006-2022, assimp team
|
Copyright (c) 2006-2023, assimp team
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ MD5Parser::MD5Parser(char *_buffer, unsigned int _fileSize) : buffer(_buffer), b
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
// Report error to the log stream
|
// Report error to the log stream
|
||||||
/*static*/ AI_WONT_RETURN void MD5Parser::ReportError(const char *error, unsigned int line) {
|
AI_WONT_RETURN void MD5Parser::ReportError(const char *error, unsigned int line) {
|
||||||
char szBuffer[1024];
|
char szBuffer[1024];
|
||||||
::ai_snprintf(szBuffer, 1024, "[MD5] Line %u: %s", line, error);
|
::ai_snprintf(szBuffer, 1024, "[MD5] Line %u: %s", line, error);
|
||||||
throw DeadlyImportError(szBuffer);
|
throw DeadlyImportError(szBuffer);
|
||||||
|
@ -95,7 +95,7 @@ MD5Parser::MD5Parser(char *_buffer, unsigned int _fileSize) : buffer(_buffer), b
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
// Report warning to the log stream
|
// Report warning to the log stream
|
||||||
/*static*/ void MD5Parser::ReportWarning(const char *warn, unsigned int line) {
|
void MD5Parser::ReportWarning(const char *warn, unsigned int line) {
|
||||||
char szBuffer[1024];
|
char szBuffer[1024];
|
||||||
::snprintf(szBuffer, sizeof(szBuffer), "[MD5] Line %u: %s", line, warn);
|
::snprintf(szBuffer, sizeof(szBuffer), "[MD5] Line %u: %s", line, warn);
|
||||||
ASSIMP_LOG_WARN(szBuffer);
|
ASSIMP_LOG_WARN(szBuffer);
|
||||||
|
@ -122,8 +122,8 @@ void MD5Parser::ParseHeader() {
|
||||||
// print the command line options to the console
|
// print the command line options to the console
|
||||||
// FIX: can break the log length limit, so we need to be careful
|
// FIX: can break the log length limit, so we need to be careful
|
||||||
char *sz = buffer;
|
char *sz = buffer;
|
||||||
while (!IsLineEnd(*buffer++))
|
while (!IsLineEnd(*buffer++));
|
||||||
;
|
|
||||||
ASSIMP_LOG_INFO(std::string(sz, std::min((uintptr_t)MAX_LOG_MESSAGE_LENGTH, (uintptr_t)(buffer - sz))));
|
ASSIMP_LOG_INFO(std::string(sz, std::min((uintptr_t)MAX_LOG_MESSAGE_LENGTH, (uintptr_t)(buffer - sz))));
|
||||||
SkipSpacesAndLineEnd();
|
SkipSpacesAndLineEnd();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,7 @@
|
||||||
Open Asset Import Library (assimp)
|
Open Asset Import Library (assimp)
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
Copyright (c) 2006-2022, assimp team
|
Copyright (c) 2006-2023, assimp team
|
||||||
|
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
|
@ -93,7 +92,7 @@ struct Section {
|
||||||
std::string mName;
|
std::string mName;
|
||||||
|
|
||||||
//! For global elements: the value of the element as string
|
//! For global elements: the value of the element as string
|
||||||
//! Iif !length() the section is not a global element
|
//! if !length() the section is not a global element
|
||||||
std::string mGlobalValue;
|
std::string mGlobalValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -349,7 +348,6 @@ public:
|
||||||
*/
|
*/
|
||||||
MD5Parser(char* buffer, unsigned int fileSize);
|
MD5Parser(char* buffer, unsigned int fileSize);
|
||||||
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
/** Report a specific error message and throw an exception
|
/** Report a specific error message and throw an exception
|
||||||
* @param error Error message to be reported
|
* @param error Error message to be reported
|
||||||
|
@ -364,47 +362,47 @@ public:
|
||||||
*/
|
*/
|
||||||
static void ReportWarning(const char* warn, unsigned int line);
|
static void ReportWarning(const char* warn, unsigned int line);
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
/** Report a specific error
|
||||||
|
* @param error Error message to be reported
|
||||||
|
*/
|
||||||
AI_WONT_RETURN void ReportError (const char* error) AI_WONT_RETURN_SUFFIX;
|
AI_WONT_RETURN void ReportError (const char* error) AI_WONT_RETURN_SUFFIX;
|
||||||
|
|
||||||
void ReportWarning (const char* warn) {
|
// -------------------------------------------------------------------
|
||||||
return ReportWarning(warn, lineNumber);
|
/** Report a specific warning
|
||||||
}
|
* @param error Warn message to be reported
|
||||||
|
*/
|
||||||
|
void ReportWarning (const char* warn);
|
||||||
|
|
||||||
//! List of all sections which have been read
|
//! List of all sections which have been read
|
||||||
SectionList mSections;
|
SectionList mSections;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// -------------------------------------------------------------------
|
|
||||||
/** Parses a file section. The current file pointer must be outside
|
|
||||||
* of a section.
|
|
||||||
* @param out Receives the section data
|
|
||||||
* @return true if the end of the file has been reached
|
|
||||||
* @throws ImportErrorException if an error occurs
|
|
||||||
*/
|
|
||||||
bool ParseSection(Section& out);
|
bool ParseSection(Section& out);
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
|
||||||
/** Parses the file header
|
|
||||||
* @throws ImportErrorException if an error occurs
|
|
||||||
*/
|
|
||||||
void ParseHeader();
|
void ParseHeader();
|
||||||
|
|
||||||
bool SkipLine(const char* in, const char** out);
|
bool SkipLine(const char* in, const char** out);
|
||||||
bool SkipLine( );
|
bool SkipLine( );
|
||||||
bool SkipSpacesAndLineEnd( const char* in, const char** out);
|
bool SkipSpacesAndLineEnd( const char* in, const char** out);
|
||||||
bool SkipSpacesAndLineEnd();
|
bool SkipSpacesAndLineEnd();
|
||||||
bool SkipSpaces();
|
bool SkipSpaces();
|
||||||
|
|
||||||
|
private:
|
||||||
char* buffer;
|
char* buffer;
|
||||||
char* bufferEnd;
|
char* bufferEnd;
|
||||||
unsigned int fileSize;
|
unsigned int fileSize;
|
||||||
unsigned int lineNumber;
|
unsigned int lineNumber;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
inline void MD5Parser::ReportWarning (const char* warn) {
|
||||||
|
return ReportWarning(warn, lineNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
inline void MD5Parser::ReportError(const char* error) {
|
inline void MD5Parser::ReportError(const char* error) {
|
||||||
ReportError(error, lineNumber);
|
ReportError(error, lineNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
inline bool MD5Parser::SkipLine(const char* in, const char** out) {
|
inline bool MD5Parser::SkipLine(const char* in, const char** out) {
|
||||||
++lineNumber;
|
++lineNumber;
|
||||||
|
@ -418,8 +416,12 @@ inline bool MD5Parser::SkipLine( ) {
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
inline bool MD5Parser::SkipSpacesAndLineEnd( const char* in, const char** out) {
|
inline bool MD5Parser::SkipSpacesAndLineEnd( const char* in, const char** out) {
|
||||||
bool bHad = false;
|
if (in == bufferEnd) {
|
||||||
bool running = true;
|
*out = in;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bHad = false, running = true;
|
||||||
while (running) {
|
while (running) {
|
||||||
if( *in == '\r' || *in == '\n') {
|
if( *in == '\r' || *in == '\n') {
|
||||||
// we open files in binary mode, so there could be \r\n sequences ...
|
// we open files in binary mode, so there could be \r\n sequences ...
|
||||||
|
@ -427,9 +429,11 @@ inline bool MD5Parser::SkipSpacesAndLineEnd( const char* in, const char** out) {
|
||||||
bHad = true;
|
bHad = true;
|
||||||
++lineNumber;
|
++lineNumber;
|
||||||
}
|
}
|
||||||
|
} else if (*in == '\t' || *in == ' ') {
|
||||||
|
bHad = false;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
else if (*in == '\t' || *in == ' ')bHad = false;
|
|
||||||
else break;
|
|
||||||
++in;
|
++in;
|
||||||
if (in == bufferEnd) {
|
if (in == bufferEnd) {
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -57,7 +57,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
using namespace Assimp;
|
namespace Assimp {
|
||||||
|
|
||||||
using namespace Assimp::Formatter;
|
using namespace Assimp::Formatter;
|
||||||
|
|
||||||
static const aiImporterDesc desc = {
|
static const aiImporterDesc desc = {
|
||||||
|
@ -73,10 +74,6 @@ static const aiImporterDesc desc = {
|
||||||
"x"
|
"x"
|
||||||
};
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
|
||||||
// Constructor to be privately used by Importer
|
|
||||||
XFileImporter::XFileImporter() = default;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
// Returns whether the class can handle the format of the given file.
|
// Returns whether the class can handle the format of the given file.
|
||||||
bool XFileImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
|
bool XFileImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
|
||||||
|
@ -124,8 +121,7 @@ void XFileImporter::InternReadFile( const std::string& pFile, aiScene* pScene, I
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
// Constructs the return data structure out of the imported data.
|
// Constructs the return data structure out of the imported data.
|
||||||
void XFileImporter::CreateDataRepresentationFromImport( aiScene* pScene, XFile::Scene* pData)
|
void XFileImporter::CreateDataRepresentationFromImport(aiScene *pScene, XFile::Scene *pData) {
|
||||||
{
|
|
||||||
// Read the global materials first so that meshes referring to them can find them later
|
// Read the global materials first so that meshes referring to them can find them later
|
||||||
ConvertMaterials(pScene, pData->mGlobalMaterials);
|
ConvertMaterials(pScene, pData->mGlobalMaterials);
|
||||||
|
|
||||||
|
@ -315,8 +311,8 @@ void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vec
|
||||||
// collect vertex data for indices of this face
|
// collect vertex data for indices of this face
|
||||||
for (unsigned int d = 0; d < df.mNumIndices; ++d) {
|
for (unsigned int d = 0; d < df.mNumIndices; ++d) {
|
||||||
df.mIndices[d] = newIndex;
|
df.mIndices[d] = newIndex;
|
||||||
const unsigned int newIdx( pf.mIndices[ d ] );
|
const unsigned int newIdx = pf.mIndices[d];
|
||||||
if ( newIdx > sourceMesh->mPositions.size() ) {
|
if (newIdx >= sourceMesh->mPositions.size()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,9 +324,11 @@ void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vec
|
||||||
if (mesh->HasNormals()) {
|
if (mesh->HasNormals()) {
|
||||||
if (sourceMesh->mNormFaces[f].mIndices.size() > d) {
|
if (sourceMesh->mNormFaces[f].mIndices.size() > d) {
|
||||||
const size_t idx(sourceMesh->mNormFaces[f].mIndices[d]);
|
const size_t idx(sourceMesh->mNormFaces[f].mIndices[d]);
|
||||||
|
if (idx < sourceMesh->mNormals.size()) {
|
||||||
mesh->mNormals[newIndex] = sourceMesh->mNormals[idx];
|
mesh->mNormals[newIndex] = sourceMesh->mNormals[idx];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// texture coord sets
|
// texture coord sets
|
||||||
for (unsigned int e = 0; e < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++e) {
|
for (unsigned int e = 0; e < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++e) {
|
||||||
|
@ -361,8 +359,11 @@ void XFileImporter::CreateMeshes( aiScene* pScene, aiNode* pNode, const std::vec
|
||||||
// set up a vertex-linear array of the weights for quick searching if a bone influences a vertex
|
// set up a vertex-linear array of the weights for quick searching if a bone influences a vertex
|
||||||
std::vector<ai_real> oldWeights(sourceMesh->mPositions.size(), 0.0);
|
std::vector<ai_real> oldWeights(sourceMesh->mPositions.size(), 0.0);
|
||||||
for (unsigned int d = 0; d < obone.mWeights.size(); ++d) {
|
for (unsigned int d = 0; d < obone.mWeights.size(); ++d) {
|
||||||
|
const unsigned int boneIdx = obone.mWeights[d].mVertex;
|
||||||
|
if (boneIdx < obone.mWeights.size()) {
|
||||||
oldWeights[obone.mWeights[d].mVertex] = obone.mWeights[d].mWeight;
|
oldWeights[obone.mWeights[d].mVertex] = obone.mWeights[d].mWeight;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// collect all vertex weights that influence a vertex in the new mesh
|
// collect all vertex weights that influence a vertex in the new mesh
|
||||||
std::vector<aiVertexWeight> newWeights;
|
std::vector<aiVertexWeight> newWeights;
|
||||||
|
@ -451,8 +452,7 @@ void XFileImporter::CreateAnimations( aiScene* pScene, const XFile::Scene* pData
|
||||||
nanim->mChannels[b] = nbone;
|
nanim->mChannels[b] = nbone;
|
||||||
|
|
||||||
// key-frames are given as combined transformation matrix keys
|
// key-frames are given as combined transformation matrix keys
|
||||||
if( !bone->mTrafoKeys.empty() )
|
if (!bone->mTrafoKeys.empty()) {
|
||||||
{
|
|
||||||
nbone->mNumPositionKeys = (unsigned int)bone->mTrafoKeys.size();
|
nbone->mNumPositionKeys = (unsigned int)bone->mTrafoKeys.size();
|
||||||
nbone->mPositionKeys = new aiVectorKey[nbone->mNumPositionKeys];
|
nbone->mPositionKeys = new aiVectorKey[nbone->mNumPositionKeys];
|
||||||
nbone->mNumRotationKeys = (unsigned int)bone->mTrafoKeys.size();
|
nbone->mNumRotationKeys = (unsigned int)bone->mTrafoKeys.size();
|
||||||
|
@ -538,8 +538,7 @@ void XFileImporter::CreateAnimations( aiScene* pScene, const XFile::Scene* pData
|
||||||
}
|
}
|
||||||
|
|
||||||
// store all converted animations in the scene
|
// store all converted animations in the scene
|
||||||
if( newAnims.size() > 0)
|
if (newAnims.size() > 0) {
|
||||||
{
|
|
||||||
pScene->mNumAnimations = (unsigned int)newAnims.size();
|
pScene->mNumAnimations = (unsigned int)newAnims.size();
|
||||||
pScene->mAnimations = new aiAnimation *[pScene->mNumAnimations];
|
pScene->mAnimations = new aiAnimation *[pScene->mNumAnimations];
|
||||||
for (unsigned int a = 0; a < newAnims.size(); a++)
|
for (unsigned int a = 0; a < newAnims.size(); a++)
|
||||||
|
@ -549,8 +548,7 @@ void XFileImporter::CreateAnimations( aiScene* pScene, const XFile::Scene* pData
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
// Converts all materials in the given array and stores them in the scene's material list.
|
// Converts all materials in the given array and stores them in the scene's material list.
|
||||||
void XFileImporter::ConvertMaterials( aiScene* pScene, std::vector<XFile::Material>& pMaterials)
|
void XFileImporter::ConvertMaterials(aiScene *pScene, std::vector<XFile::Material> &pMaterials) {
|
||||||
{
|
|
||||||
// count the non-referrer materials in the array
|
// count the non-referrer materials in the array
|
||||||
unsigned int numNewMaterials(0);
|
unsigned int numNewMaterials(0);
|
||||||
for (unsigned int a = 0; a < pMaterials.size(); ++a) {
|
for (unsigned int a = 0; a < pMaterials.size(); ++a) {
|
||||||
|
@ -599,8 +597,7 @@ void XFileImporter::ConvertMaterials( aiScene* pScene, std::vector<XFile::Materi
|
||||||
// Shading model: hard-coded to PHONG, there is no such information in an XFile
|
// Shading model: hard-coded to PHONG, there is no such information in an XFile
|
||||||
// FIX (aramis): If the specular exponent is 0, use gouraud shading. This is a bugfix
|
// FIX (aramis): If the specular exponent is 0, use gouraud shading. This is a bugfix
|
||||||
// for some models in the SDK (e.g. good old tiny.x)
|
// for some models in the SDK (e.g. good old tiny.x)
|
||||||
int shadeMode = (int)oldMat.mSpecularExponent == 0.0f
|
int shadeMode = (int)oldMat.mSpecularExponent == 0.0f ? aiShadingMode_Gouraud : aiShadingMode_Phong;
|
||||||
? aiShadingMode_Gouraud : aiShadingMode_Phong;
|
|
||||||
|
|
||||||
mat->AddProperty<int>(&shadeMode, 1, AI_MATKEY_SHADING_MODEL);
|
mat->AddProperty<int>(&shadeMode, 1, AI_MATKEY_SHADING_MODEL);
|
||||||
// material colours
|
// material colours
|
||||||
|
@ -611,7 +608,6 @@ void XFileImporter::ConvertMaterials( aiScene* pScene, std::vector<XFile::Materi
|
||||||
mat->AddProperty(&oldMat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR);
|
mat->AddProperty(&oldMat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR);
|
||||||
mat->AddProperty(&oldMat.mSpecularExponent, 1, AI_MATKEY_SHININESS);
|
mat->AddProperty(&oldMat.mSpecularExponent, 1, AI_MATKEY_SHININESS);
|
||||||
|
|
||||||
|
|
||||||
// texture, if there is one
|
// texture, if there is one
|
||||||
if (1 == oldMat.mTextures.size()) {
|
if (1 == oldMat.mTextures.size()) {
|
||||||
const XFile::TexEntry &otex = oldMat.mTextures.back();
|
const XFile::TexEntry &otex = oldMat.mTextures.back();
|
||||||
|
@ -679,4 +675,6 @@ void XFileImporter::ConvertMaterials( aiScene* pScene, std::vector<XFile::Materi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace Assimp
|
||||||
|
|
||||||
#endif // !! ASSIMP_BUILD_NO_X_IMPORTER
|
#endif // !! ASSIMP_BUILD_NO_X_IMPORTER
|
||||||
|
|
|
@ -68,7 +68,7 @@ namespace XFile {
|
||||||
*/
|
*/
|
||||||
class XFileImporter : public BaseImporter {
|
class XFileImporter : public BaseImporter {
|
||||||
public:
|
public:
|
||||||
XFileImporter();
|
XFileImporter() = default;
|
||||||
~XFileImporter() override = default;
|
~XFileImporter() override = default;
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
|
@ -172,22 +172,6 @@ static void IdentityMatrix4(mat4 &o) {
|
||||||
o[15] = 1;
|
o[15] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool IsBoneWeightFitted(vec4 &weight) {
|
|
||||||
return weight[0] + weight[1] + weight[2] + weight[3] >= 1.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int FitBoneWeight(vec4 &weight, float value) {
|
|
||||||
int i = 0;
|
|
||||||
for (; i < 4; ++i) {
|
|
||||||
if (weight[i] < value) {
|
|
||||||
weight[i] = value;
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void SetAccessorRange(Ref<Accessor> acc, void *data, size_t count,
|
void SetAccessorRange(Ref<Accessor> acc, void *data, size_t count,
|
||||||
unsigned int numCompsIn, unsigned int numCompsOut) {
|
unsigned int numCompsIn, unsigned int numCompsOut) {
|
||||||
|
@ -1009,23 +993,29 @@ Ref<Node> FindSkeletonRootJoint(Ref<Skin> &skinRef) {
|
||||||
return parentNodeRef;
|
return parentNodeRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct boneIndexWeightPair {
|
||||||
|
unsigned int indexJoint;
|
||||||
|
float weight;
|
||||||
|
bool operator()(boneIndexWeightPair &a, boneIndexWeightPair &b) {
|
||||||
|
return a.weight > b.weight;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
void ExportSkin(Asset &mAsset, const aiMesh *aimesh, Ref<Mesh> &meshRef, Ref<Buffer> &bufferRef, Ref<Skin> &skinRef,
|
void ExportSkin(Asset &mAsset, const aiMesh *aimesh, Ref<Mesh> &meshRef, Ref<Buffer> &bufferRef, Ref<Skin> &skinRef,
|
||||||
std::vector<aiMatrix4x4> &inverseBindMatricesData) {
|
std::vector<aiMatrix4x4> &inverseBindMatricesData, bool unlimitedBonesPerVertex) {
|
||||||
if (aimesh->mNumBones < 1) {
|
if (aimesh->mNumBones < 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the vertex joint and weight data.
|
// Store the vertex joint and weight data.
|
||||||
const size_t NumVerts(aimesh->mNumVertices);
|
const size_t NumVerts(aimesh->mNumVertices);
|
||||||
vec4 *vertexJointData = new vec4[NumVerts];
|
|
||||||
vec4 *vertexWeightData = new vec4[NumVerts];
|
|
||||||
int *jointsPerVertex = new int[NumVerts];
|
int *jointsPerVertex = new int[NumVerts];
|
||||||
|
std::vector<std::vector<boneIndexWeightPair>> allVerticesPairs;
|
||||||
|
int maxJointsPerVertex = 0;
|
||||||
for (size_t i = 0; i < NumVerts; ++i) {
|
for (size_t i = 0; i < NumVerts; ++i) {
|
||||||
jointsPerVertex[i] = 0;
|
jointsPerVertex[i] = 0;
|
||||||
for (size_t j = 0; j < 4; ++j) {
|
std::vector<boneIndexWeightPair> vertexPair;
|
||||||
vertexJointData[i][j] = 0;
|
allVerticesPairs.push_back(vertexPair);
|
||||||
vertexWeightData[i][j] = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (unsigned int idx_bone = 0; idx_bone < aimesh->mNumBones; ++idx_bone) {
|
for (unsigned int idx_bone = 0; idx_bone < aimesh->mNumBones; ++idx_bone) {
|
||||||
|
@ -1055,38 +1045,63 @@ void ExportSkin(Asset &mAsset, const aiMesh *aimesh, Ref<Mesh> &meshRef, Ref<Buf
|
||||||
jointNamesIndex = static_cast<unsigned int>(inverseBindMatricesData.size() - 1);
|
jointNamesIndex = static_cast<unsigned int>(inverseBindMatricesData.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// aib->mWeights =====> vertexWeightData
|
// aib->mWeights =====> temp pairs data
|
||||||
for (unsigned int idx_weights = 0; idx_weights < aib->mNumWeights; ++idx_weights) {
|
for (unsigned int idx_weights = 0; idx_weights < aib->mNumWeights;
|
||||||
|
++idx_weights) {
|
||||||
unsigned int vertexId = aib->mWeights[idx_weights].mVertexId;
|
unsigned int vertexId = aib->mWeights[idx_weights].mVertexId;
|
||||||
float vertWeight = aib->mWeights[idx_weights].mWeight;
|
float vertWeight = aib->mWeights[idx_weights].mWeight;
|
||||||
|
allVerticesPairs[vertexId].push_back({jointNamesIndex, vertWeight});
|
||||||
// A vertex can only have at most four joint weights, which ideally sum up to 1
|
|
||||||
if (IsBoneWeightFitted(vertexWeightData[vertexId])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (jointsPerVertex[vertexId] > 3) {
|
|
||||||
int boneIndexFitted = FitBoneWeight(vertexWeightData[vertexId], vertWeight);
|
|
||||||
if (boneIndexFitted != -1) {
|
|
||||||
vertexJointData[vertexId][boneIndexFitted] = static_cast<float>(jointNamesIndex);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vertexJointData[vertexId][jointsPerVertex[vertexId]] = static_cast<float>(jointNamesIndex);
|
|
||||||
vertexWeightData[vertexId][jointsPerVertex[vertexId]] = vertWeight;
|
|
||||||
|
|
||||||
jointsPerVertex[vertexId] += 1;
|
jointsPerVertex[vertexId] += 1;
|
||||||
|
maxJointsPerVertex =
|
||||||
|
std::max(maxJointsPerVertex, jointsPerVertex[vertexId]);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
} // End: for-loop mNumMeshes
|
} // End: for-loop mNumMeshes
|
||||||
|
|
||||||
|
if (!unlimitedBonesPerVertex){
|
||||||
|
// skinning limited only for 4 bones per vertex, default
|
||||||
|
maxJointsPerVertex = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// temp pairs data =====> vertexWeightData
|
||||||
|
size_t numGroups = (maxJointsPerVertex - 1) / 4 + 1;
|
||||||
|
vec4 *vertexJointData = new vec4[NumVerts * numGroups];
|
||||||
|
vec4 *vertexWeightData = new vec4[NumVerts * numGroups];
|
||||||
|
for (size_t indexVertex = 0; indexVertex < NumVerts; ++indexVertex) {
|
||||||
|
// order pairs by weight for each vertex
|
||||||
|
std::sort(allVerticesPairs[indexVertex].begin(),
|
||||||
|
allVerticesPairs[indexVertex].end(),
|
||||||
|
boneIndexWeightPair());
|
||||||
|
for (size_t indexGroup = 0; indexGroup < numGroups; ++indexGroup) {
|
||||||
|
for (size_t indexJoint = 0; indexJoint < 4; ++indexJoint) {
|
||||||
|
size_t indexBone = indexGroup * 4 + indexJoint;
|
||||||
|
size_t indexData = indexVertex + NumVerts * indexGroup;
|
||||||
|
if (indexBone >= allVerticesPairs[indexVertex].size()) {
|
||||||
|
vertexJointData[indexData][indexJoint] = 0.f;
|
||||||
|
vertexWeightData[indexData][indexJoint] = 0.f;
|
||||||
|
} else {
|
||||||
|
vertexJointData[indexData][indexJoint] =
|
||||||
|
static_cast<float>(
|
||||||
|
allVerticesPairs[indexVertex][indexBone].indexJoint);
|
||||||
|
vertexWeightData[indexData][indexJoint] =
|
||||||
|
allVerticesPairs[indexVertex][indexBone].weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t idx_group = 0; idx_group < numGroups; ++idx_group) {
|
||||||
Mesh::Primitive &p = meshRef->primitives.back();
|
Mesh::Primitive &p = meshRef->primitives.back();
|
||||||
Ref<Accessor> vertexJointAccessor = ExportData(mAsset, skinRef->id, bufferRef, aimesh->mNumVertices,
|
Ref<Accessor> vertexJointAccessor = ExportData(
|
||||||
vertexJointData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT);
|
mAsset, skinRef->id, bufferRef, aimesh->mNumVertices,
|
||||||
|
vertexJointData + idx_group * NumVerts,
|
||||||
|
AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT);
|
||||||
if (vertexJointAccessor) {
|
if (vertexJointAccessor) {
|
||||||
size_t offset = vertexJointAccessor->bufferView->byteOffset;
|
size_t offset = vertexJointAccessor->bufferView->byteOffset;
|
||||||
size_t bytesLen = vertexJointAccessor->bufferView->byteLength;
|
size_t bytesLen = vertexJointAccessor->bufferView->byteLength;
|
||||||
unsigned int s_bytesPerComp = ComponentTypeSize(ComponentType_UNSIGNED_SHORT);
|
unsigned int s_bytesPerComp =
|
||||||
unsigned int bytesPerComp = ComponentTypeSize(vertexJointAccessor->componentType);
|
ComponentTypeSize(ComponentType_UNSIGNED_SHORT);
|
||||||
|
unsigned int bytesPerComp =
|
||||||
|
ComponentTypeSize(vertexJointAccessor->componentType);
|
||||||
size_t s_bytesLen = bytesLen * s_bytesPerComp / bytesPerComp;
|
size_t s_bytesLen = bytesLen * s_bytesPerComp / bytesPerComp;
|
||||||
Ref<Buffer> buf = vertexJointAccessor->bufferView->buffer;
|
Ref<Buffer> buf = vertexJointAccessor->bufferView->buffer;
|
||||||
uint8_t *arrys = new uint8_t[bytesLen];
|
uint8_t *arrys = new uint8_t[bytesLen];
|
||||||
|
@ -1105,12 +1120,14 @@ void ExportSkin(Asset &mAsset, const aiMesh *aimesh, Ref<Mesh> &meshRef, Ref<Buf
|
||||||
p.attributes.joint.push_back(vertexJointAccessor);
|
p.attributes.joint.push_back(vertexJointAccessor);
|
||||||
delete[] arrys;
|
delete[] arrys;
|
||||||
}
|
}
|
||||||
|
Ref<Accessor> vertexWeightAccessor = ExportData(
|
||||||
Ref<Accessor> vertexWeightAccessor = ExportData(mAsset, skinRef->id, bufferRef, aimesh->mNumVertices,
|
mAsset, skinRef->id, bufferRef, aimesh->mNumVertices,
|
||||||
vertexWeightData, AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT);
|
vertexWeightData + idx_group * NumVerts,
|
||||||
|
AttribType::VEC4, AttribType::VEC4, ComponentType_FLOAT);
|
||||||
if (vertexWeightAccessor) {
|
if (vertexWeightAccessor) {
|
||||||
p.attributes.weight.push_back(vertexWeightAccessor);
|
p.attributes.weight.push_back(vertexWeightAccessor);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
delete[] jointsPerVertex;
|
delete[] jointsPerVertex;
|
||||||
delete[] vertexWeightData;
|
delete[] vertexWeightData;
|
||||||
delete[] vertexJointData;
|
delete[] vertexJointData;
|
||||||
|
@ -1247,9 +1264,19 @@ void glTF2Exporter::ExportMeshes() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /*************** Skins ****************/
|
||||||
|
// if (aim->HasBones()) {
|
||||||
|
// ExportSkin(*mAsset, aim, m, b, skinRef, inverseBindMatricesData);
|
||||||
|
// }
|
||||||
/*************** Skins ****************/
|
/*************** Skins ****************/
|
||||||
if (aim->HasBones()) {
|
if (aim->HasBones()) {
|
||||||
ExportSkin(*mAsset, aim, m, b, skinRef, inverseBindMatricesData);
|
bool unlimitedBonesPerVertex =
|
||||||
|
this->mProperties->HasPropertyBool(
|
||||||
|
AI_CONFIG_EXPORT_GLTF_UNLIMITED_SKINNING_BONES_PER_VERTEX) &&
|
||||||
|
this->mProperties->GetPropertyBool(
|
||||||
|
AI_CONFIG_EXPORT_GLTF_UNLIMITED_SKINNING_BONES_PER_VERTEX);
|
||||||
|
ExportSkin(*mAsset, aim, m, b, skinRef, inverseBindMatricesData,
|
||||||
|
unlimitedBonesPerVertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*************** Targets for blendshapes ****************/
|
/*************** Targets for blendshapes ****************/
|
||||||
|
|
|
@ -923,26 +923,26 @@ IF(ASSIMP_HUNTER_ENABLED)
|
||||||
hunter_add_package(utf8)
|
hunter_add_package(utf8)
|
||||||
find_package(utf8cpp CONFIG REQUIRED)
|
find_package(utf8cpp CONFIG REQUIRED)
|
||||||
ELSE()
|
ELSE()
|
||||||
# utf8 is header-only, so Assimp doesn't need to do anything.
|
INCLUDE_DIRECTORIES("../contrib/utf8cpp/source")
|
||||||
ENDIF()
|
ENDIF()
|
||||||
|
|
||||||
# polyclipping
|
# polyclipping
|
||||||
IF(ASSIMP_HUNTER_ENABLED)
|
#IF(ASSIMP_HUNTER_ENABLED)
|
||||||
hunter_add_package(polyclipping)
|
# hunter_add_package(polyclipping)
|
||||||
find_package(polyclipping CONFIG REQUIRED)
|
# find_package(polyclipping CONFIG REQUIRED)
|
||||||
ELSE()
|
#ELSE()
|
||||||
SET( Clipper_SRCS
|
SET( Clipper_SRCS
|
||||||
../contrib/clipper/clipper.hpp
|
../contrib/clipper/clipper.hpp
|
||||||
../contrib/clipper/clipper.cpp
|
../contrib/clipper/clipper.cpp
|
||||||
)
|
)
|
||||||
SOURCE_GROUP( Contrib\\Clipper FILES ${Clipper_SRCS})
|
SOURCE_GROUP( Contrib\\Clipper FILES ${Clipper_SRCS})
|
||||||
ENDIF()
|
#ENDIF()
|
||||||
|
|
||||||
# poly2tri
|
# poly2tri
|
||||||
IF(ASSIMP_HUNTER_ENABLED)
|
#IF(ASSIMP_HUNTER_ENABLED)
|
||||||
hunter_add_package(poly2tri)
|
# hunter_add_package(poly2tri)
|
||||||
find_package(poly2tri CONFIG REQUIRED)
|
# find_package(poly2tri CONFIG REQUIRED)
|
||||||
ELSE()
|
#ELSE()
|
||||||
SET( Poly2Tri_SRCS
|
SET( Poly2Tri_SRCS
|
||||||
../contrib/poly2tri/poly2tri/common/shapes.cc
|
../contrib/poly2tri/poly2tri/common/shapes.cc
|
||||||
../contrib/poly2tri/poly2tri/common/shapes.h
|
../contrib/poly2tri/poly2tri/common/shapes.h
|
||||||
|
@ -957,7 +957,7 @@ ELSE()
|
||||||
../contrib/poly2tri/poly2tri/sweep/sweep_context.h
|
../contrib/poly2tri/poly2tri/sweep/sweep_context.h
|
||||||
)
|
)
|
||||||
SOURCE_GROUP( Contrib\\Poly2Tri FILES ${Poly2Tri_SRCS})
|
SOURCE_GROUP( Contrib\\Poly2Tri FILES ${Poly2Tri_SRCS})
|
||||||
ENDIF()
|
#ENDIF()
|
||||||
|
|
||||||
# minizip/unzip
|
# minizip/unzip
|
||||||
IF(ASSIMP_HUNTER_ENABLED)
|
IF(ASSIMP_HUNTER_ENABLED)
|
||||||
|
@ -1267,9 +1267,9 @@ TARGET_INCLUDE_DIRECTORIES ( assimp PUBLIC
|
||||||
IF(ASSIMP_HUNTER_ENABLED)
|
IF(ASSIMP_HUNTER_ENABLED)
|
||||||
TARGET_LINK_LIBRARIES(assimp
|
TARGET_LINK_LIBRARIES(assimp
|
||||||
PUBLIC
|
PUBLIC
|
||||||
polyclipping::polyclipping
|
#polyclipping::polyclipping
|
||||||
openddlparser::openddl_parser
|
openddlparser::openddl_parser
|
||||||
poly2tri::poly2tri
|
#poly2tri::poly2tri
|
||||||
minizip::minizip
|
minizip::minizip
|
||||||
ZLIB::zlib
|
ZLIB::zlib
|
||||||
RapidJSON::rapidjson
|
RapidJSON::rapidjson
|
||||||
|
@ -1380,7 +1380,12 @@ ENDIF()
|
||||||
IF(NOT ASSIMP_HUNTER_ENABLED)
|
IF(NOT ASSIMP_HUNTER_ENABLED)
|
||||||
if (UNZIP_FOUND)
|
if (UNZIP_FOUND)
|
||||||
INCLUDE_DIRECTORIES(${UNZIP_INCLUDE_DIRS})
|
INCLUDE_DIRECTORIES(${UNZIP_INCLUDE_DIRS})
|
||||||
|
# TODO if cmake required version has been updated to >3.12.0, collapse this to the second case only
|
||||||
|
if(${CMAKE_VERSION} VERSION_LESS "3.12.0")
|
||||||
TARGET_LINK_LIBRARIES(assimp ${UNZIP_LIBRARIES})
|
TARGET_LINK_LIBRARIES(assimp ${UNZIP_LIBRARIES})
|
||||||
|
else()
|
||||||
|
TARGET_LINK_LIBRARIES(assimp ${UNZIP_LINK_LIBRARIES})
|
||||||
|
endif()
|
||||||
else ()
|
else ()
|
||||||
INCLUDE_DIRECTORIES("../")
|
INCLUDE_DIRECTORIES("../")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
Open Asset Import Library (assimp)
|
Open Asset Import Library (assimp)
|
||||||
---------------------------------------------------------------------------
|
---------------------------------------------------------------------------
|
||||||
|
|
||||||
Copyright (c) 2006-2022, assimp team
|
Copyright (c) 2006-2023, assimp team
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
|
@ -49,10 +49,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------
|
||||||
// Legal information string - don't remove this.
|
// Legal information string - don't remove this.
|
||||||
static const char *LEGAL_INFORMATION =
|
static constexpr char LEGAL_INFORMATION[] =
|
||||||
"Open Asset Import Library (Assimp).\n"
|
"Open Asset Import Library (Assimp).\n"
|
||||||
"A free C/C++ library to import various 3D file formats into applications\n\n"
|
"A free C/C++ library to import various 3D file formats into applications\n\n"
|
||||||
"(c) 2006-2022, Assimp team\n"
|
"(c) 2006-2023, Assimp team\n"
|
||||||
"License under the terms and conditions of the 3-clause BSD license\n"
|
"License under the terms and conditions of the 3-clause BSD license\n"
|
||||||
"https://www.assimp.org\n";
|
"https://www.assimp.org\n";
|
||||||
|
|
||||||
|
@ -150,9 +150,11 @@ ASSIMP_API aiScene::~aiScene() {
|
||||||
// To make sure we won't crash if the data is invalid it's
|
// To make sure we won't crash if the data is invalid it's
|
||||||
// much better to check whether both mNumXXX and mXXX are
|
// much better to check whether both mNumXXX and mXXX are
|
||||||
// valid instead of relying on just one of them.
|
// valid instead of relying on just one of them.
|
||||||
if (mNumMeshes && mMeshes)
|
if (mNumMeshes && mMeshes) {
|
||||||
for (unsigned int a = 0; a < mNumMeshes; a++)
|
for (unsigned int a = 0; a < mNumMeshes; ++a) {
|
||||||
delete mMeshes[a];
|
delete mMeshes[a];
|
||||||
|
}
|
||||||
|
}
|
||||||
delete[] mMeshes;
|
delete[] mMeshes;
|
||||||
|
|
||||||
if (mNumMaterials && mMaterials) {
|
if (mNumMaterials && mMaterials) {
|
||||||
|
@ -162,24 +164,32 @@ ASSIMP_API aiScene::~aiScene() {
|
||||||
}
|
}
|
||||||
delete[] mMaterials;
|
delete[] mMaterials;
|
||||||
|
|
||||||
if (mNumAnimations && mAnimations)
|
if (mNumAnimations && mAnimations) {
|
||||||
for (unsigned int a = 0; a < mNumAnimations; a++)
|
for (unsigned int a = 0; a < mNumAnimations; ++a) {
|
||||||
delete mAnimations[a];
|
delete mAnimations[a];
|
||||||
|
}
|
||||||
|
}
|
||||||
delete[] mAnimations;
|
delete[] mAnimations;
|
||||||
|
|
||||||
if (mNumTextures && mTextures)
|
if (mNumTextures && mTextures) {
|
||||||
for (unsigned int a = 0; a < mNumTextures; a++)
|
for (unsigned int a = 0; a < mNumTextures; ++a) {
|
||||||
delete mTextures[a];
|
delete mTextures[a];
|
||||||
|
}
|
||||||
|
}
|
||||||
delete[] mTextures;
|
delete[] mTextures;
|
||||||
|
|
||||||
if (mNumLights && mLights)
|
if (mNumLights && mLights) {
|
||||||
for (unsigned int a = 0; a < mNumLights; a++)
|
for (unsigned int a = 0; a < mNumLights; ++a) {
|
||||||
delete mLights[a];
|
delete mLights[a];
|
||||||
|
}
|
||||||
|
}
|
||||||
delete[] mLights;
|
delete[] mLights;
|
||||||
|
|
||||||
if (mNumCameras && mCameras)
|
if (mNumCameras && mCameras) {
|
||||||
for (unsigned int a = 0; a < mNumCameras; a++)
|
for (unsigned int a = 0; a < mNumCameras; ++a) {
|
||||||
delete mCameras[a];
|
delete mCameras[a];
|
||||||
|
}
|
||||||
|
}
|
||||||
delete[] mCameras;
|
delete[] mCameras;
|
||||||
|
|
||||||
aiMetadata::Dealloc(mMetaData);
|
aiMetadata::Dealloc(mMetaData);
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
Open Asset Import Library (assimp)
|
Open Asset Import Library (assimp)
|
||||||
---------------------------------------------------------------------------
|
---------------------------------------------------------------------------
|
||||||
|
|
||||||
Copyright (c) 2006-2022, assimp team
|
Copyright (c) 2006-2023, assimp team
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
|
@ -273,7 +273,8 @@ void FindInvalidDataProcess::ProcessAnimation(aiAnimation *anim) {
|
||||||
void FindInvalidDataProcess::ProcessAnimationChannel(aiNodeAnim *anim) {
|
void FindInvalidDataProcess::ProcessAnimationChannel(aiNodeAnim *anim) {
|
||||||
ai_assert(nullptr != anim);
|
ai_assert(nullptr != anim);
|
||||||
if (anim->mNumPositionKeys == 0 && anim->mNumRotationKeys == 0 && anim->mNumScalingKeys == 0) {
|
if (anim->mNumPositionKeys == 0 && anim->mNumRotationKeys == 0 && anim->mNumScalingKeys == 0) {
|
||||||
ai_assert_entry();
|
ASSIMP_LOG_ERROR("Invalid node anuimation instance detected.");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,6 @@ Open Asset Import Library (assimp)
|
||||||
|
|
||||||
Copyright (c) 2006-2022, assimp team
|
Copyright (c) 2006-2022, assimp team
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use of this software in source and binary forms,
|
Redistribution and use of this software in source and binary forms,
|
||||||
|
@ -109,6 +107,7 @@ bool GenVertexNormalsProcess::GenMeshVertexNormals(aiMesh *pMesh, unsigned int m
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
delete[] pMesh->mNormals;
|
delete[] pMesh->mNormals;
|
||||||
|
pMesh->mNormals = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the mesh consists of lines and/or points but not of
|
// If the mesh consists of lines and/or points but not of
|
||||||
|
|
|
@ -3,9 +3,7 @@
|
||||||
Open Asset Import Library (assimp)
|
Open Asset Import Library (assimp)
|
||||||
---------------------------------------------------------------------------
|
---------------------------------------------------------------------------
|
||||||
|
|
||||||
Copyright (c) 2006-2022, assimp team
|
Copyright (c) 2006-2023, assimp team
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
|
@ -59,12 +57,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
|
|
||||||
using namespace Assimp;
|
namespace Assimp {
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
// Constructor to be privately used by Importer
|
// Constructor to be privately used by Importer
|
||||||
ImproveCacheLocalityProcess::ImproveCacheLocalityProcess()
|
ImproveCacheLocalityProcess::ImproveCacheLocalityProcess() :
|
||||||
: mConfigCacheDepth(PP_ICL_PTCACHE_SIZE) {
|
mConfigCacheDepth(PP_ICL_PTCACHE_SIZE) {
|
||||||
// empty
|
// empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,36 +108,13 @@ void ImproveCacheLocalityProcess::Execute( aiScene* pScene) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
// Improves the cache coherency of a specific mesh
|
static ai_real calculateInputACMR(aiMesh *pMesh, const aiFace *const pcEnd,
|
||||||
ai_real ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshNum) {
|
unsigned int configCacheDepth, unsigned int meshNum) {
|
||||||
// TODO: rewrite this to use std::vector or boost::shared_array
|
ai_real fACMR = 0.0f;
|
||||||
ai_assert(nullptr != pMesh);
|
unsigned int *piFIFOStack = new unsigned int[configCacheDepth];
|
||||||
|
memset(piFIFOStack, 0xff, configCacheDepth * sizeof(unsigned int));
|
||||||
// Check whether the input data is valid
|
|
||||||
// - there must be vertices and faces
|
|
||||||
// - all faces must be triangulated or we can't operate on them
|
|
||||||
if (!pMesh->HasFaces() || !pMesh->HasPositions())
|
|
||||||
return static_cast<ai_real>(0.f);
|
|
||||||
|
|
||||||
if (pMesh->mPrimitiveTypes != aiPrimitiveType_TRIANGLE) {
|
|
||||||
ASSIMP_LOG_ERROR("This algorithm works on triangle meshes only");
|
|
||||||
return static_cast<ai_real>(0.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(pMesh->mNumVertices <= mConfigCacheDepth) {
|
|
||||||
return static_cast<ai_real>(0.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
ai_real fACMR = 3.f;
|
|
||||||
const aiFace* const pcEnd = pMesh->mFaces+pMesh->mNumFaces;
|
|
||||||
|
|
||||||
// Input ACMR is for logging purposes only
|
|
||||||
if (!DefaultLogger::isNullLogger()) {
|
|
||||||
|
|
||||||
unsigned int* piFIFOStack = new unsigned int[mConfigCacheDepth];
|
|
||||||
memset(piFIFOStack,0xff,mConfigCacheDepth*sizeof(unsigned int));
|
|
||||||
unsigned int *piCur = piFIFOStack;
|
unsigned int *piCur = piFIFOStack;
|
||||||
const unsigned int* const piCurEnd = piFIFOStack + mConfigCacheDepth;
|
const unsigned int *const piCurEnd = piFIFOStack + configCacheDepth;
|
||||||
|
|
||||||
// count the number of cache misses
|
// count the number of cache misses
|
||||||
unsigned int iCacheMisses = 0;
|
unsigned int iCacheMisses = 0;
|
||||||
|
@ -174,21 +149,53 @@ ai_real ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int me
|
||||||
ASSIMP_LOG_WARN(szBuff);
|
ASSIMP_LOG_WARN(szBuff);
|
||||||
return static_cast<ai_real>(0.f);
|
return static_cast<ai_real>(0.f);
|
||||||
}
|
}
|
||||||
|
return fACMR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
// Improves the cache coherency of a specific mesh
|
||||||
|
ai_real ImproveCacheLocalityProcess::ProcessMesh(aiMesh *pMesh, unsigned int meshNum) {
|
||||||
|
// TODO: rewrite this to use std::vector or boost::shared_array
|
||||||
|
ai_assert(nullptr != pMesh);
|
||||||
|
|
||||||
|
// Check whether the input data is valid
|
||||||
|
// - there must be vertices and faces
|
||||||
|
// - all faces must be triangulated or we can't operate on them
|
||||||
|
if (!pMesh->HasFaces() || !pMesh->HasPositions())
|
||||||
|
return static_cast<ai_real>(0.f);
|
||||||
|
|
||||||
|
if (pMesh->mPrimitiveTypes != aiPrimitiveType_TRIANGLE) {
|
||||||
|
ASSIMP_LOG_ERROR("This algorithm works on triangle meshes only");
|
||||||
|
return static_cast<ai_real>(0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pMesh->mNumVertices <= mConfigCacheDepth) {
|
||||||
|
return static_cast<ai_real>(0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
ai_real fACMR = 3.f;
|
||||||
|
const aiFace *const pcEnd = pMesh->mFaces + pMesh->mNumFaces;
|
||||||
|
|
||||||
|
// Input ACMR is for logging purposes only
|
||||||
|
if (!DefaultLogger::isNullLogger()) {
|
||||||
|
fACMR = calculateInputACMR(pMesh, pcEnd, mConfigCacheDepth, meshNum);
|
||||||
}
|
}
|
||||||
|
|
||||||
// first we need to build a vertex-triangle adjacency list
|
// first we need to build a vertex-triangle adjacency list
|
||||||
VertexTriangleAdjacency adj(pMesh->mFaces, pMesh->mNumFaces, pMesh->mNumVertices, true);
|
VertexTriangleAdjacency adj(pMesh->mFaces, pMesh->mNumFaces, pMesh->mNumVertices, true);
|
||||||
|
|
||||||
// build a list to store per-vertex caching time stamps
|
// build a list to store per-vertex caching time stamps
|
||||||
unsigned int* const piCachingStamps = new unsigned int[pMesh->mNumVertices];
|
std::vector<unsigned int> piCachingStamps;
|
||||||
memset(piCachingStamps,0x0,pMesh->mNumVertices*sizeof(unsigned int));
|
piCachingStamps.resize(pMesh->mNumVertices);
|
||||||
|
memset(&piCachingStamps[0], 0x0, pMesh->mNumVertices * sizeof(unsigned int));
|
||||||
|
|
||||||
// allocate an empty output index buffer. We store the output indices in one large array.
|
// allocate an empty output index buffer. We store the output indices in one large array.
|
||||||
// Since the number of triangles won't change the input faces can be reused. This is how
|
// Since the number of triangles won't change the input faces can be reused. This is how
|
||||||
// we save thousands of redundant mini allocations for aiFace::mIndices
|
// we save thousands of redundant mini allocations for aiFace::mIndices
|
||||||
const unsigned int iIdxCnt = pMesh->mNumFaces * 3;
|
const unsigned int iIdxCnt = pMesh->mNumFaces * 3;
|
||||||
unsigned int* const piIBOutput = new unsigned int[iIdxCnt];
|
std::vector<unsigned int> piIBOutput;
|
||||||
unsigned int* piCSIter = piIBOutput;
|
piIBOutput.resize(iIdxCnt);
|
||||||
|
std::vector<unsigned int>::iterator piCSIter = piIBOutput.begin();
|
||||||
|
|
||||||
// allocate the flag array to hold the information
|
// allocate the flag array to hold the information
|
||||||
// whether a face has already been emitted or not
|
// whether a face has already been emitted or not
|
||||||
|
@ -202,7 +209,8 @@ ai_real ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int me
|
||||||
const std::vector<unsigned int> piNumTriPtrNoModify(piNumTriPtr, piNumTriPtr + pMesh->mNumVertices);
|
const std::vector<unsigned int> piNumTriPtrNoModify(piNumTriPtr, piNumTriPtr + pMesh->mNumVertices);
|
||||||
|
|
||||||
// get the largest number of referenced triangles and allocate the "candidate buffer"
|
// get the largest number of referenced triangles and allocate the "candidate buffer"
|
||||||
unsigned int iMaxRefTris = 0; {
|
unsigned int iMaxRefTris = 0;
|
||||||
|
{
|
||||||
const unsigned int *piCur = adj.mLiveTriangles;
|
const unsigned int *piCur = adj.mLiveTriangles;
|
||||||
const unsigned int *const piCurEnd = adj.mLiveTriangles + pMesh->mNumVertices;
|
const unsigned int *const piCurEnd = adj.mLiveTriangles + pMesh->mNumVertices;
|
||||||
for (; piCur != piCurEnd; ++piCur) {
|
for (; piCur != piCurEnd; ++piCur) {
|
||||||
|
@ -210,7 +218,8 @@ ai_real ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int me
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ai_assert(iMaxRefTris > 0);
|
ai_assert(iMaxRefTris > 0);
|
||||||
unsigned int* piCandidates = new unsigned int[iMaxRefTris*3];
|
std::vector<unsigned int> piCandidates;
|
||||||
|
piCandidates.resize(iMaxRefTris * 3);
|
||||||
unsigned int iCacheMisses = 0;
|
unsigned int iCacheMisses = 0;
|
||||||
|
|
||||||
// ...................................................................................
|
// ...................................................................................
|
||||||
|
@ -250,7 +259,7 @@ ai_real ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int me
|
||||||
|
|
||||||
unsigned int icnt = piNumTriPtrNoModify[ivdx];
|
unsigned int icnt = piNumTriPtrNoModify[ivdx];
|
||||||
unsigned int *piList = adj.GetAdjacentTriangles(ivdx);
|
unsigned int *piList = adj.GetAdjacentTriangles(ivdx);
|
||||||
unsigned int* piCurCandidate = piCandidates;
|
std::vector<unsigned int>::iterator piCurCandidate = piCandidates.begin();
|
||||||
|
|
||||||
// get all triangles in the neighborhood
|
// get all triangles in the neighborhood
|
||||||
for (unsigned int tri = 0; tri < icnt; ++tri) {
|
for (unsigned int tri = 0; tri < icnt; ++tri) {
|
||||||
|
@ -261,7 +270,7 @@ ai_real ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int me
|
||||||
|
|
||||||
// so iterate through all vertices of the current triangle
|
// so iterate through all vertices of the current triangle
|
||||||
const aiFace *pcFace = &pMesh->mFaces[fidx];
|
const aiFace *pcFace = &pMesh->mFaces[fidx];
|
||||||
unsigned nind = pcFace->mNumIndices;
|
const unsigned nind = pcFace->mNumIndices;
|
||||||
for (unsigned ind = 0; ind < nind; ind++) {
|
for (unsigned ind = 0; ind < nind; ind++) {
|
||||||
unsigned dp = pcFace->mIndices[ind];
|
unsigned dp = pcFace->mIndices[ind];
|
||||||
|
|
||||||
|
@ -297,7 +306,7 @@ ai_real ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int me
|
||||||
// get next fanning vertex
|
// get next fanning vertex
|
||||||
ivdx = -1;
|
ivdx = -1;
|
||||||
int max_priority = -1;
|
int max_priority = -1;
|
||||||
for (unsigned int* piCur = piCandidates;piCur != piCurCandidate;++piCur) {
|
for (std::vector<unsigned int>::iterator piCur = piCandidates.begin(); piCur != piCurCandidate; ++piCur) {
|
||||||
const unsigned int dp = *piCur;
|
const unsigned int dp = *piCur;
|
||||||
|
|
||||||
// must have live triangles
|
// must have live triangles
|
||||||
|
@ -345,29 +354,29 @@ ai_real ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int me
|
||||||
}
|
}
|
||||||
ai_real fACMR2 = 0.0f;
|
ai_real fACMR2 = 0.0f;
|
||||||
if (!DefaultLogger::isNullLogger()) {
|
if (!DefaultLogger::isNullLogger()) {
|
||||||
fACMR2 = (float)iCacheMisses / pMesh->mNumFaces;
|
fACMR2 = static_cast<ai_real>(iCacheMisses / pMesh->mNumFaces);
|
||||||
|
const ai_real averageACMR = ((fACMR - fACMR2) / fACMR) * 100.f;
|
||||||
// very intense verbose logging ... prepare for much text if there are many meshes
|
// very intense verbose logging ... prepare for much text if there are many meshes
|
||||||
if (DefaultLogger::get()->getLogSeverity() == Logger::VERBOSE) {
|
if (DefaultLogger::get()->getLogSeverity() == Logger::VERBOSE) {
|
||||||
ASSIMP_LOG_VERBOSE_DEBUG("Mesh %u | ACMR in: ", meshNum, " out: ", fACMR, " | ~", fACMR2, ((fACMR - fACMR2) / fACMR) * 100.f);
|
ASSIMP_LOG_VERBOSE_DEBUG("Mesh ", meshNum, "| ACMR in: ", fACMR, " out: ", fACMR2, " | average ACMR ", averageACMR);
|
||||||
}
|
}
|
||||||
|
|
||||||
fACMR2 *= pMesh->mNumFaces;
|
fACMR2 *= pMesh->mNumFaces;
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort the output index buffer back to the input array
|
// sort the output index buffer back to the input array
|
||||||
piCSIter = piIBOutput;
|
piCSIter = piIBOutput.begin();
|
||||||
for (aiFace *pcFace = pMesh->mFaces; pcFace != pcEnd; ++pcFace) {
|
for (aiFace *pcFace = pMesh->mFaces; pcFace != pcEnd; ++pcFace) {
|
||||||
unsigned nind = pcFace->mNumIndices;
|
unsigned nind = pcFace->mNumIndices;
|
||||||
unsigned *ind = pcFace->mIndices;
|
unsigned *ind = pcFace->mIndices;
|
||||||
if (nind > 0) ind[0] = *piCSIter++;
|
if (nind > 0)
|
||||||
if (nind > 1) ind[1] = *piCSIter++;
|
ind[0] = *piCSIter++;
|
||||||
if (nind > 2) ind[2] = *piCSIter++;
|
if (nind > 1)
|
||||||
|
ind[1] = *piCSIter++;
|
||||||
|
if (nind > 2)
|
||||||
|
ind[2] = *piCSIter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete temporary storage
|
|
||||||
delete[] piCachingStamps;
|
|
||||||
delete[] piIBOutput;
|
|
||||||
delete[] piCandidates;
|
|
||||||
|
|
||||||
return fACMR2;
|
return fACMR2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace Assimp
|
||||||
|
|
|
@ -53,6 +53,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
using namespace Assimp;
|
using namespace Assimp;
|
||||||
|
|
||||||
|
@ -145,7 +146,7 @@ bool areVerticesEqual(
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class XMesh>
|
template<class XMesh>
|
||||||
void updateXMeshVertices(XMesh *pMesh, std::vector<Vertex> &uniqueVertices) {
|
void updateXMeshVertices(XMesh *pMesh, std::vector<int> &uniqueVertices) {
|
||||||
// replace vertex data with the unique data sets
|
// replace vertex data with the unique data sets
|
||||||
pMesh->mNumVertices = (unsigned int)uniqueVertices.size();
|
pMesh->mNumVertices = (unsigned int)uniqueVertices.size();
|
||||||
|
|
||||||
|
@ -157,52 +158,46 @@ void updateXMeshVertices(XMesh *pMesh, std::vector<Vertex> &uniqueVertices) {
|
||||||
|
|
||||||
// Position, if present (check made for aiAnimMesh)
|
// Position, if present (check made for aiAnimMesh)
|
||||||
if (pMesh->mVertices) {
|
if (pMesh->mVertices) {
|
||||||
delete [] pMesh->mVertices;
|
std::unique_ptr<aiVector3D[]> oldVertices(pMesh->mVertices);
|
||||||
pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
|
pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
|
||||||
for (unsigned int a = 0; a < pMesh->mNumVertices; a++) {
|
for (unsigned int a = 0; a < pMesh->mNumVertices; a++)
|
||||||
pMesh->mVertices[a] = uniqueVertices[a].position;
|
pMesh->mVertices[a] = oldVertices[uniqueVertices[a]];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normals, if present
|
// Normals, if present
|
||||||
if (pMesh->mNormals) {
|
if (pMesh->mNormals) {
|
||||||
delete [] pMesh->mNormals;
|
std::unique_ptr<aiVector3D[]> oldNormals(pMesh->mNormals);
|
||||||
pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
|
pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
|
||||||
for( unsigned int a = 0; a < pMesh->mNumVertices; a++) {
|
for (unsigned int a = 0; a < pMesh->mNumVertices; a++)
|
||||||
pMesh->mNormals[a] = uniqueVertices[a].normal;
|
pMesh->mNormals[a] = oldNormals[uniqueVertices[a]];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Tangents, if present
|
// Tangents, if present
|
||||||
if (pMesh->mTangents) {
|
if (pMesh->mTangents) {
|
||||||
delete [] pMesh->mTangents;
|
std::unique_ptr<aiVector3D[]> oldTangents(pMesh->mTangents);
|
||||||
pMesh->mTangents = new aiVector3D[pMesh->mNumVertices];
|
pMesh->mTangents = new aiVector3D[pMesh->mNumVertices];
|
||||||
for (unsigned int a = 0; a < pMesh->mNumVertices; a++) {
|
for (unsigned int a = 0; a < pMesh->mNumVertices; a++)
|
||||||
pMesh->mTangents[a] = uniqueVertices[a].tangent;
|
pMesh->mTangents[a] = oldTangents[uniqueVertices[a]];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Bitangents as well
|
// Bitangents as well
|
||||||
if (pMesh->mBitangents) {
|
if (pMesh->mBitangents) {
|
||||||
delete [] pMesh->mBitangents;
|
std::unique_ptr<aiVector3D[]> oldBitangents(pMesh->mBitangents);
|
||||||
pMesh->mBitangents = new aiVector3D[pMesh->mNumVertices];
|
pMesh->mBitangents = new aiVector3D[pMesh->mNumVertices];
|
||||||
for (unsigned int a = 0; a < pMesh->mNumVertices; a++) {
|
for (unsigned int a = 0; a < pMesh->mNumVertices; a++)
|
||||||
pMesh->mBitangents[a] = uniqueVertices[a].bitangent;
|
pMesh->mBitangents[a] = oldBitangents[uniqueVertices[a]];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Vertex colors
|
// Vertex colors
|
||||||
for (unsigned int a = 0; pMesh->HasVertexColors(a); a++) {
|
for (unsigned int a = 0; pMesh->HasVertexColors(a); a++) {
|
||||||
delete [] pMesh->mColors[a];
|
std::unique_ptr<aiColor4D[]> oldColors(pMesh->mColors[a]);
|
||||||
pMesh->mColors[a] = new aiColor4D[pMesh->mNumVertices];
|
pMesh->mColors[a] = new aiColor4D[pMesh->mNumVertices];
|
||||||
for( unsigned int b = 0; b < pMesh->mNumVertices; b++) {
|
for (unsigned int b = 0; b < pMesh->mNumVertices; b++)
|
||||||
pMesh->mColors[a][b] = uniqueVertices[b].colors[a];
|
pMesh->mColors[a][b] = oldColors[uniqueVertices[b]];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Texture coords
|
// Texture coords
|
||||||
for (unsigned int a = 0; pMesh->HasTextureCoords(a); a++) {
|
for (unsigned int a = 0; pMesh->HasTextureCoords(a); a++) {
|
||||||
delete [] pMesh->mTextureCoords[a];
|
std::unique_ptr<aiVector3D[]> oldTextureCoords(pMesh->mTextureCoords[a]);
|
||||||
pMesh->mTextureCoords[a] = new aiVector3D[pMesh->mNumVertices];
|
pMesh->mTextureCoords[a] = new aiVector3D[pMesh->mNumVertices];
|
||||||
for (unsigned int b = 0; b < pMesh->mNumVertices; b++) {
|
for (unsigned int b = 0; b < pMesh->mNumVertices; b++)
|
||||||
pMesh->mTextureCoords[a][b] = uniqueVertices[b].texcoords[a];
|
pMesh->mTextureCoords[a][b] = oldTextureCoords[uniqueVertices[b]];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,7 +265,7 @@ int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll never have more vertices afterwards.
|
// We'll never have more vertices afterwards.
|
||||||
std::vector<Vertex> uniqueVertices;
|
std::vector<int> uniqueVertices;
|
||||||
uniqueVertices.reserve( pMesh->mNumVertices);
|
uniqueVertices.reserve( pMesh->mNumVertices);
|
||||||
|
|
||||||
// For each vertex the index of the vertex it was replaced by.
|
// For each vertex the index of the vertex it was replaced by.
|
||||||
|
@ -311,7 +306,7 @@ int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex) {
|
||||||
const bool hasAnimMeshes = pMesh->mNumAnimMeshes > 0;
|
const bool hasAnimMeshes = pMesh->mNumAnimMeshes > 0;
|
||||||
|
|
||||||
// We'll never have more vertices afterwards.
|
// We'll never have more vertices afterwards.
|
||||||
std::vector<std::vector<Vertex>> uniqueAnimatedVertices;
|
std::vector<std::vector<int>> uniqueAnimatedVertices;
|
||||||
if (hasAnimMeshes) {
|
if (hasAnimMeshes) {
|
||||||
uniqueAnimatedVertices.resize(pMesh->mNumAnimMeshes);
|
uniqueAnimatedVertices.resize(pMesh->mNumAnimMeshes);
|
||||||
for (unsigned int animMeshIndex = 0; animMeshIndex < pMesh->mNumAnimMeshes; animMeshIndex++) {
|
for (unsigned int animMeshIndex = 0; animMeshIndex < pMesh->mNumAnimMeshes; animMeshIndex++) {
|
||||||
|
@ -345,10 +340,10 @@ int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex) {
|
||||||
//keep track of its index and increment 1
|
//keep track of its index and increment 1
|
||||||
replaceIndex[a] = newIndex++;
|
replaceIndex[a] = newIndex++;
|
||||||
// add the vertex to the unique vertices
|
// add the vertex to the unique vertices
|
||||||
uniqueVertices.push_back(v);
|
uniqueVertices.push_back(a);
|
||||||
if (hasAnimMeshes) {
|
if (hasAnimMeshes) {
|
||||||
for (unsigned int animMeshIndex = 0; animMeshIndex < pMesh->mNumAnimMeshes; animMeshIndex++) {
|
for (unsigned int animMeshIndex = 0; animMeshIndex < pMesh->mNumAnimMeshes; animMeshIndex++) {
|
||||||
uniqueAnimatedVertices[animMeshIndex].emplace_back(pMesh->mAnimMeshes[animMeshIndex], a);
|
uniqueAnimatedVertices[animMeshIndex].emplace_back(a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else{
|
} else{
|
||||||
|
|
|
@ -290,12 +290,6 @@ void PretransformVertices::ComputeAbsoluteTransform(aiNode *pcNode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void normalizeVectorArray(aiVector3D *vectorArrayIn, aiVector3D *vectorArrayOut, size_t numVectors) {
|
|
||||||
for (size_t i=0; i<numVectors; ++i) {
|
|
||||||
vectorArrayOut[i] = vectorArrayIn[i].Normalize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
// Apply the node transformation to a mesh
|
// Apply the node transformation to a mesh
|
||||||
void PretransformVertices::ApplyTransform(aiMesh *mesh, const aiMatrix4x4 &mat) const {
|
void PretransformVertices::ApplyTransform(aiMesh *mesh, const aiMatrix4x4 &mat) const {
|
||||||
|
@ -322,8 +316,11 @@ void PretransformVertices::ApplyTransform(aiMesh *mesh, const aiMatrix4x4 &mat)
|
||||||
const aiMatrix3x3 m = aiMatrix3x3(mat).Inverse().Transpose();
|
const aiMatrix3x3 m = aiMatrix3x3(mat).Inverse().Transpose();
|
||||||
|
|
||||||
if (mesh->HasNormals()) {
|
if (mesh->HasNormals()) {
|
||||||
normalizeVectorArray(mesh->mNormals, mesh->mNormals, mesh->mNumVertices);
|
for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
|
||||||
|
mesh->mNormals[i] = (m * mesh->mNormals[i]).Normalize();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mesh->HasTangentsAndBitangents()) {
|
if (mesh->HasTangentsAndBitangents()) {
|
||||||
for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
|
for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
|
||||||
mesh->mTangents[i] = (m * mesh->mTangents[i]).Normalize();
|
mesh->mTangents[i] = (m * mesh->mTangents[i]).Normalize();
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
The Clipper code library, the "Software" (that includes Delphi, C++ & C#
|
|
||||||
source code, accompanying samples and documentation), has been released
|
|
||||||
under the following license, terms and conditions:
|
|
||||||
|
|
||||||
Boost Software License - Version 1.0 - August 17th, 2003
|
Boost Software License - Version 1.0 - August 17th, 2003
|
||||||
http://www.boost.org/LICENSE_1_0.txt
|
http://www.boost.org/LICENSE_1_0.txt
|
||||||
|
|
||||||
|
@ -26,4 +22,3 @@ SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
DEALINGS IN THE SOFTWARE.
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,10 +1,10 @@
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* *
|
* *
|
||||||
* Author : Angus Johnson *
|
* Author : Angus Johnson *
|
||||||
* Version : 4.8.8 *
|
* Version : 6.4.2 *
|
||||||
* Date : 30 August 2012 *
|
* Date : 27 February 2017 *
|
||||||
* Website : http://www.angusj.com *
|
* Website : http://www.angusj.com *
|
||||||
* Copyright : Angus Johnson 2010-2012 *
|
* Copyright : Angus Johnson 2010-2017 *
|
||||||
* *
|
* *
|
||||||
* License: *
|
* License: *
|
||||||
* Use, modification & distribution is subject to Boost Software License Ver 1. *
|
* Use, modification & distribution is subject to Boost Software License Ver 1. *
|
||||||
|
@ -34,11 +34,30 @@
|
||||||
#ifndef clipper_hpp
|
#ifndef clipper_hpp
|
||||||
#define clipper_hpp
|
#define clipper_hpp
|
||||||
|
|
||||||
|
#define CLIPPER_VERSION "6.4.2"
|
||||||
|
|
||||||
|
//use_int32: When enabled 32bit ints are used instead of 64bit ints. This
|
||||||
|
//improve performance but coordinate values are limited to the range +/- 46340
|
||||||
|
//#define use_int32
|
||||||
|
|
||||||
|
//use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance.
|
||||||
|
//#define use_xyz
|
||||||
|
|
||||||
|
//use_lines: Enables line clipping. Adds a very minor cost to performance.
|
||||||
|
#define use_lines
|
||||||
|
|
||||||
|
//use_deprecated: Enables temporary support for the obsolete functions
|
||||||
|
//#define use_deprecated
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <list>
|
||||||
|
#include <set>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
#include <functional>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
namespace ClipperLib {
|
namespace ClipperLib {
|
||||||
|
|
||||||
|
@ -50,129 +69,150 @@ enum PolyType { ptSubject, ptClip };
|
||||||
//see http://glprogramming.com/red/chapter11.html
|
//see http://glprogramming.com/red/chapter11.html
|
||||||
enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative };
|
enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative };
|
||||||
|
|
||||||
typedef signed long long long64;
|
#ifdef use_int32
|
||||||
|
typedef int cInt;
|
||||||
|
static cInt const loRange = 0x7FFF;
|
||||||
|
static cInt const hiRange = 0x7FFF;
|
||||||
|
#else
|
||||||
|
typedef signed long long cInt;
|
||||||
|
static cInt const loRange = 0x3FFFFFFF;
|
||||||
|
static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL;
|
||||||
|
typedef signed long long long64; //used by Int128 class
|
||||||
typedef unsigned long long ulong64;
|
typedef unsigned long long ulong64;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
struct IntPoint {
|
struct IntPoint {
|
||||||
public:
|
cInt X;
|
||||||
long64 X;
|
cInt Y;
|
||||||
long64 Y;
|
#ifdef use_xyz
|
||||||
IntPoint(long64 x = 0, long64 y = 0): X(x), Y(y) {};
|
cInt Z;
|
||||||
friend std::ostream& operator <<(std::ostream &s, IntPoint &p);
|
IntPoint(cInt x = 0, cInt y = 0, cInt z = 0): X(x), Y(y), Z(z) {};
|
||||||
|
#else
|
||||||
|
IntPoint(cInt x = 0, cInt y = 0): X(x), Y(y) {};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
friend inline bool operator== (const IntPoint& a, const IntPoint& b)
|
||||||
|
{
|
||||||
|
return a.X == b.X && a.Y == b.Y;
|
||||||
|
}
|
||||||
|
friend inline bool operator!= (const IntPoint& a, const IntPoint& b)
|
||||||
|
{
|
||||||
|
return a.X != b.X || a.Y != b.Y;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
typedef std::vector< IntPoint > Polygon;
|
typedef std::vector< IntPoint > Path;
|
||||||
typedef std::vector< Polygon > Polygons;
|
typedef std::vector< Path > Paths;
|
||||||
|
|
||||||
std::ostream& operator <<(std::ostream &s, Polygon &p);
|
inline Path& operator <<(Path& poly, const IntPoint& p) {poly.push_back(p); return poly;}
|
||||||
std::ostream& operator <<(std::ostream &s, Polygons &p);
|
inline Paths& operator <<(Paths& polys, const Path& p) {polys.push_back(p); return polys;}
|
||||||
|
|
||||||
struct ExPolygon {
|
std::ostream& operator <<(std::ostream &s, const IntPoint &p);
|
||||||
Polygon outer;
|
std::ostream& operator <<(std::ostream &s, const Path &p);
|
||||||
Polygons holes;
|
std::ostream& operator <<(std::ostream &s, const Paths &p);
|
||||||
|
|
||||||
|
struct DoublePoint
|
||||||
|
{
|
||||||
|
double X;
|
||||||
|
double Y;
|
||||||
|
DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {}
|
||||||
|
DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {}
|
||||||
};
|
};
|
||||||
typedef std::vector< ExPolygon > ExPolygons;
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#ifdef use_xyz
|
||||||
|
typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4};
|
||||||
enum JoinType {jtSquare, jtRound, jtMiter};
|
enum JoinType {jtSquare, jtRound, jtMiter};
|
||||||
|
enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound};
|
||||||
|
|
||||||
bool Orientation(const Polygon &poly);
|
class PolyNode;
|
||||||
double Area(const Polygon &poly);
|
typedef std::vector< PolyNode* > PolyNodes;
|
||||||
void OffsetPolygons(const Polygons &in_polys, Polygons &out_polys,
|
|
||||||
double delta, JoinType jointype = jtSquare, double MiterLimit = 2);
|
|
||||||
void SimplifyPolygon(const Polygon &in_poly, Polygons &out_polys, PolyFillType fillType = pftEvenOdd);
|
|
||||||
void SimplifyPolygons(const Polygons &in_polys, Polygons &out_polys, PolyFillType fillType = pftEvenOdd);
|
|
||||||
void SimplifyPolygons(Polygons &polys, PolyFillType fillType = pftEvenOdd);
|
|
||||||
|
|
||||||
void ReversePolygon(Polygon& p);
|
class PolyNode
|
||||||
void ReversePolygons(Polygons& p);
|
{
|
||||||
|
public:
|
||||||
//used internally ...
|
PolyNode();
|
||||||
enum EdgeSide { esNeither = 0, esLeft = 1, esRight = 2, esBoth = 3 };
|
virtual ~PolyNode(){};
|
||||||
enum IntersectProtects { ipNone = 0, ipLeft = 1, ipRight = 2, ipBoth = 3 };
|
Path Contour;
|
||||||
|
PolyNodes Childs;
|
||||||
struct TEdge {
|
PolyNode* Parent;
|
||||||
long64 xbot;
|
PolyNode* GetNext() const;
|
||||||
long64 ybot;
|
bool IsHole() const;
|
||||||
long64 xcurr;
|
bool IsOpen() const;
|
||||||
long64 ycurr;
|
int ChildCount() const;
|
||||||
long64 xtop;
|
private:
|
||||||
long64 ytop;
|
//PolyNode& operator =(PolyNode& other);
|
||||||
double dx;
|
unsigned Index; //node index in Parent.Childs
|
||||||
long64 tmpX;
|
bool m_IsOpen;
|
||||||
PolyType polyType;
|
JoinType m_jointype;
|
||||||
EdgeSide side;
|
EndType m_endtype;
|
||||||
int windDelta; //1 or -1 depending on winding direction
|
PolyNode* GetNextSiblingUp() const;
|
||||||
int windCnt;
|
void AddChild(PolyNode& child);
|
||||||
int windCnt2; //winding count of the opposite polytype
|
friend class Clipper; //to access Index
|
||||||
int outIdx;
|
friend class ClipperOffset;
|
||||||
TEdge *next;
|
|
||||||
TEdge *prev;
|
|
||||||
TEdge *nextInLML;
|
|
||||||
TEdge *nextInAEL;
|
|
||||||
TEdge *prevInAEL;
|
|
||||||
TEdge *nextInSEL;
|
|
||||||
TEdge *prevInSEL;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct IntersectNode {
|
class PolyTree: public PolyNode
|
||||||
TEdge *edge1;
|
{
|
||||||
TEdge *edge2;
|
public:
|
||||||
IntPoint pt;
|
~PolyTree(){ Clear(); };
|
||||||
IntersectNode *next;
|
PolyNode* GetFirst() const;
|
||||||
|
void Clear();
|
||||||
|
int Total() const;
|
||||||
|
private:
|
||||||
|
//PolyTree& operator =(PolyTree& other);
|
||||||
|
PolyNodes AllNodes;
|
||||||
|
friend class Clipper; //to access AllNodes
|
||||||
};
|
};
|
||||||
|
|
||||||
struct LocalMinima {
|
bool Orientation(const Path &poly);
|
||||||
long64 Y;
|
double Area(const Path &poly);
|
||||||
TEdge *leftBound;
|
int PointInPolygon(const IntPoint &pt, const Path &path);
|
||||||
TEdge *rightBound;
|
|
||||||
LocalMinima *next;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Scanbeam {
|
void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd);
|
||||||
long64 Y;
|
void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd);
|
||||||
Scanbeam *next;
|
void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd);
|
||||||
};
|
|
||||||
|
|
||||||
struct OutPt; //forward declaration
|
void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415);
|
||||||
|
void CleanPolygon(Path& poly, double distance = 1.415);
|
||||||
|
void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.415);
|
||||||
|
void CleanPolygons(Paths& polys, double distance = 1.415);
|
||||||
|
|
||||||
struct OutRec {
|
void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed);
|
||||||
int idx;
|
void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed);
|
||||||
bool isHole;
|
void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution);
|
||||||
OutRec *FirstLeft;
|
|
||||||
OutRec *AppendLink;
|
|
||||||
OutPt *pts;
|
|
||||||
OutPt *bottomPt;
|
|
||||||
OutPt *bottomFlag;
|
|
||||||
EdgeSide sides;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct OutPt {
|
void PolyTreeToPaths(const PolyTree& polytree, Paths& paths);
|
||||||
int idx;
|
void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths);
|
||||||
IntPoint pt;
|
void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths);
|
||||||
OutPt *next;
|
|
||||||
OutPt *prev;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct JoinRec {
|
void ReversePath(Path& p);
|
||||||
IntPoint pt1a;
|
void ReversePaths(Paths& p);
|
||||||
IntPoint pt1b;
|
|
||||||
int poly1Idx;
|
|
||||||
IntPoint pt2a;
|
|
||||||
IntPoint pt2b;
|
|
||||||
int poly2Idx;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HorzJoinRec {
|
struct IntRect { cInt left; cInt top; cInt right; cInt bottom; };
|
||||||
TEdge *edge;
|
|
||||||
int savedIdx;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct IntRect { long64 left; long64 top; long64 right; long64 bottom; };
|
//enums that are used internally ...
|
||||||
|
enum EdgeSide { esLeft = 1, esRight = 2};
|
||||||
|
|
||||||
|
//forward declarations (for stuff used internally) ...
|
||||||
|
struct TEdge;
|
||||||
|
struct IntersectNode;
|
||||||
|
struct LocalMinimum;
|
||||||
|
struct OutPt;
|
||||||
|
struct OutRec;
|
||||||
|
struct Join;
|
||||||
|
|
||||||
typedef std::vector < OutRec* > PolyOutList;
|
typedef std::vector < OutRec* > PolyOutList;
|
||||||
typedef std::vector < TEdge* > EdgeList;
|
typedef std::vector < TEdge* > EdgeList;
|
||||||
typedef std::vector < JoinRec* > JoinList;
|
typedef std::vector < Join* > JoinList;
|
||||||
typedef std::vector < HorzJoinRec* > HorzJoinList;
|
typedef std::vector < IntersectNode* > IntersectList;
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
//ClipperBase is the ancestor to the Clipper class. It should not be
|
//ClipperBase is the ancestor to the Clipper class. It should not be
|
||||||
//instantiated directly. This class simply abstracts the conversion of sets of
|
//instantiated directly. This class simply abstracts the conversion of sets of
|
||||||
|
@ -182,110 +222,170 @@ class ClipperBase
|
||||||
public:
|
public:
|
||||||
ClipperBase();
|
ClipperBase();
|
||||||
virtual ~ClipperBase();
|
virtual ~ClipperBase();
|
||||||
bool AddPolygon(const Polygon &pg, PolyType polyType);
|
virtual bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed);
|
||||||
bool AddPolygons( const Polygons &ppg, PolyType polyType);
|
bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed);
|
||||||
virtual void Clear();
|
virtual void Clear();
|
||||||
IntRect GetBounds();
|
IntRect GetBounds();
|
||||||
|
bool PreserveCollinear() {return m_PreserveCollinear;};
|
||||||
|
void PreserveCollinear(bool value) {m_PreserveCollinear = value;};
|
||||||
protected:
|
protected:
|
||||||
void DisposeLocalMinimaList();
|
void DisposeLocalMinimaList();
|
||||||
TEdge* AddBoundsToLML(TEdge *e);
|
TEdge* AddBoundsToLML(TEdge *e, bool IsClosed);
|
||||||
void PopLocalMinima();
|
|
||||||
virtual void Reset();
|
virtual void Reset();
|
||||||
void InsertLocalMinima(LocalMinima *newLm);
|
TEdge* ProcessBound(TEdge* E, bool IsClockwise);
|
||||||
LocalMinima *m_CurrentLM;
|
void InsertScanbeam(const cInt Y);
|
||||||
LocalMinima *m_MinimaList;
|
bool PopScanbeam(cInt &Y);
|
||||||
|
bool LocalMinimaPending();
|
||||||
|
bool PopLocalMinima(cInt Y, const LocalMinimum *&locMin);
|
||||||
|
OutRec* CreateOutRec();
|
||||||
|
void DisposeAllOutRecs();
|
||||||
|
void DisposeOutRec(PolyOutList::size_type index);
|
||||||
|
void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2);
|
||||||
|
void DeleteFromAEL(TEdge *e);
|
||||||
|
void UpdateEdgeIntoAEL(TEdge *&e);
|
||||||
|
|
||||||
|
typedef std::vector<LocalMinimum> MinimaList;
|
||||||
|
MinimaList::iterator m_CurrentLM;
|
||||||
|
MinimaList m_MinimaList;
|
||||||
|
|
||||||
bool m_UseFullRange;
|
bool m_UseFullRange;
|
||||||
EdgeList m_edges;
|
EdgeList m_edges;
|
||||||
|
bool m_PreserveCollinear;
|
||||||
|
bool m_HasOpenPaths;
|
||||||
|
PolyOutList m_PolyOuts;
|
||||||
|
TEdge *m_ActiveEdges;
|
||||||
|
|
||||||
|
typedef std::priority_queue<cInt> ScanbeamList;
|
||||||
|
ScanbeamList m_Scanbeam;
|
||||||
};
|
};
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
class Clipper : public virtual ClipperBase
|
class Clipper : public virtual ClipperBase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Clipper();
|
Clipper(int initOptions = 0);
|
||||||
~Clipper();
|
|
||||||
bool Execute(ClipType clipType,
|
bool Execute(ClipType clipType,
|
||||||
Polygons &solution,
|
Paths &solution,
|
||||||
PolyFillType subjFillType = pftEvenOdd,
|
PolyFillType fillType = pftEvenOdd);
|
||||||
PolyFillType clipFillType = pftEvenOdd);
|
|
||||||
bool Execute(ClipType clipType,
|
bool Execute(ClipType clipType,
|
||||||
ExPolygons &solution,
|
Paths &solution,
|
||||||
PolyFillType subjFillType = pftEvenOdd,
|
PolyFillType subjFillType,
|
||||||
PolyFillType clipFillType = pftEvenOdd);
|
PolyFillType clipFillType);
|
||||||
void Clear();
|
bool Execute(ClipType clipType,
|
||||||
|
PolyTree &polytree,
|
||||||
|
PolyFillType fillType = pftEvenOdd);
|
||||||
|
bool Execute(ClipType clipType,
|
||||||
|
PolyTree &polytree,
|
||||||
|
PolyFillType subjFillType,
|
||||||
|
PolyFillType clipFillType);
|
||||||
bool ReverseSolution() { return m_ReverseOutput; };
|
bool ReverseSolution() { return m_ReverseOutput; };
|
||||||
void ReverseSolution(bool value) {m_ReverseOutput = value;};
|
void ReverseSolution(bool value) {m_ReverseOutput = value;};
|
||||||
|
bool StrictlySimple() {return m_StrictSimple;};
|
||||||
|
void StrictlySimple(bool value) {m_StrictSimple = value;};
|
||||||
|
//set the callback function for z value filling on intersections (otherwise Z is 0)
|
||||||
|
#ifdef use_xyz
|
||||||
|
void ZFillFunction(ZFillCallback zFillFunc);
|
||||||
|
#endif
|
||||||
protected:
|
protected:
|
||||||
void Reset();
|
virtual bool ExecuteInternal();
|
||||||
virtual bool ExecuteInternal(bool fixHoleLinkages);
|
|
||||||
private:
|
private:
|
||||||
PolyOutList m_PolyOuts;
|
|
||||||
JoinList m_Joins;
|
JoinList m_Joins;
|
||||||
HorzJoinList m_HorizJoins;
|
JoinList m_GhostJoins;
|
||||||
|
IntersectList m_IntersectList;
|
||||||
ClipType m_ClipType;
|
ClipType m_ClipType;
|
||||||
Scanbeam *m_Scanbeam;
|
typedef std::list<cInt> MaximaList;
|
||||||
TEdge *m_ActiveEdges;
|
MaximaList m_Maxima;
|
||||||
TEdge *m_SortedEdges;
|
TEdge *m_SortedEdges;
|
||||||
IntersectNode *m_IntersectNodes;
|
|
||||||
bool m_ExecuteLocked;
|
bool m_ExecuteLocked;
|
||||||
PolyFillType m_ClipFillType;
|
PolyFillType m_ClipFillType;
|
||||||
PolyFillType m_SubjFillType;
|
PolyFillType m_SubjFillType;
|
||||||
bool m_ReverseOutput;
|
bool m_ReverseOutput;
|
||||||
void DisposeScanbeamList();
|
bool m_UsingPolyTree;
|
||||||
|
bool m_StrictSimple;
|
||||||
|
#ifdef use_xyz
|
||||||
|
ZFillCallback m_ZFill; //custom callback
|
||||||
|
#endif
|
||||||
void SetWindingCount(TEdge& edge);
|
void SetWindingCount(TEdge& edge);
|
||||||
bool IsEvenOddFillType(const TEdge& edge) const;
|
bool IsEvenOddFillType(const TEdge& edge) const;
|
||||||
bool IsEvenOddAltFillType(const TEdge& edge) const;
|
bool IsEvenOddAltFillType(const TEdge& edge) const;
|
||||||
void InsertScanbeam(const long64 Y);
|
void InsertLocalMinimaIntoAEL(const cInt botY);
|
||||||
long64 PopScanbeam();
|
void InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge);
|
||||||
void InsertLocalMinimaIntoAEL(const long64 botY);
|
|
||||||
void InsertEdgeIntoAEL(TEdge *edge);
|
|
||||||
void AddEdgeToSEL(TEdge *edge);
|
void AddEdgeToSEL(TEdge *edge);
|
||||||
|
bool PopEdgeFromSEL(TEdge *&edge);
|
||||||
void CopyAELToSEL();
|
void CopyAELToSEL();
|
||||||
void DeleteFromSEL(TEdge *e);
|
void DeleteFromSEL(TEdge *e);
|
||||||
void DeleteFromAEL(TEdge *e);
|
|
||||||
void UpdateEdgeIntoAEL(TEdge *&e);
|
|
||||||
void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2);
|
void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2);
|
||||||
bool IsContributing(const TEdge& edge) const;
|
bool IsContributing(const TEdge& edge) const;
|
||||||
bool IsTopHorz(const long64 XPos);
|
bool IsTopHorz(const cInt XPos);
|
||||||
void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2);
|
void DoMaxima(TEdge *e);
|
||||||
void DoMaxima(TEdge *e, long64 topY);
|
|
||||||
void ProcessHorizontals();
|
void ProcessHorizontals();
|
||||||
void ProcessHorizontal(TEdge *horzEdge);
|
void ProcessHorizontal(TEdge *horzEdge);
|
||||||
void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
|
void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
|
||||||
void AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
|
OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
|
||||||
|
OutRec* GetOutRec(int idx);
|
||||||
void AppendPolygon(TEdge *e1, TEdge *e2);
|
void AppendPolygon(TEdge *e1, TEdge *e2);
|
||||||
void DoEdge1(TEdge *edge1, TEdge *edge2, const IntPoint &pt);
|
void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt);
|
||||||
void DoEdge2(TEdge *edge1, TEdge *edge2, const IntPoint &pt);
|
OutPt* AddOutPt(TEdge *e, const IntPoint &pt);
|
||||||
void DoBothEdges(TEdge *edge1, TEdge *edge2, const IntPoint &pt);
|
OutPt* GetLastOutPt(TEdge *e);
|
||||||
void IntersectEdges(TEdge *e1, TEdge *e2,
|
bool ProcessIntersections(const cInt topY);
|
||||||
const IntPoint &pt, IntersectProtects protects);
|
void BuildIntersectList(const cInt topY);
|
||||||
OutRec* CreateOutRec();
|
|
||||||
void AddOutPt(TEdge *e, const IntPoint &pt);
|
|
||||||
void DisposeBottomPt(OutRec &outRec);
|
|
||||||
void DisposeAllPolyPts();
|
|
||||||
void DisposeOutRec(PolyOutList::size_type index);
|
|
||||||
bool ProcessIntersections(const long64 botY, const long64 topY);
|
|
||||||
void AddIntersectNode(TEdge *e1, TEdge *e2, const IntPoint &pt);
|
|
||||||
void BuildIntersectList(const long64 botY, const long64 topY);
|
|
||||||
void ProcessIntersectList();
|
void ProcessIntersectList();
|
||||||
void ProcessEdgesAtTopOfScanbeam(const long64 topY);
|
void ProcessEdgesAtTopOfScanbeam(const cInt topY);
|
||||||
void BuildResult(Polygons& polys);
|
void BuildResult(Paths& polys);
|
||||||
void BuildResultEx(ExPolygons& polys);
|
void BuildResult2(PolyTree& polytree);
|
||||||
void SetHoleState(TEdge *e, OutRec *OutRec);
|
void SetHoleState(TEdge *e, OutRec *outrec);
|
||||||
void DisposeIntersectNodes();
|
void DisposeIntersectNodes();
|
||||||
bool FixupIntersections();
|
bool FixupIntersectionOrder();
|
||||||
void FixupOutPolygon(OutRec &outRec);
|
void FixupOutPolygon(OutRec &outrec);
|
||||||
|
void FixupOutPolyline(OutRec &outrec);
|
||||||
bool IsHole(TEdge *e);
|
bool IsHole(TEdge *e);
|
||||||
void FixHoleLinkage(OutRec *outRec);
|
bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl);
|
||||||
void CheckHoleLinkages1(OutRec *outRec1, OutRec *outRec2);
|
void FixHoleLinkage(OutRec &outrec);
|
||||||
void CheckHoleLinkages2(OutRec *outRec1, OutRec *outRec2);
|
void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt);
|
||||||
void AddJoin(TEdge *e1, TEdge *e2, int e1OutIdx = -1, int e2OutIdx = -1);
|
|
||||||
void ClearJoins();
|
void ClearJoins();
|
||||||
void AddHorzJoin(TEdge *e, int idx);
|
void ClearGhostJoins();
|
||||||
void ClearHorzJoins();
|
void AddGhostJoin(OutPt *op, const IntPoint offPt);
|
||||||
void JoinCommonEdges(bool fixHoleLinkages);
|
bool JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2);
|
||||||
|
void JoinCommonEdges();
|
||||||
|
void DoSimplePolygons();
|
||||||
|
void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec);
|
||||||
|
void FixupFirstLefts2(OutRec* InnerOutRec, OutRec* OuterOutRec);
|
||||||
|
void FixupFirstLefts3(OutRec* OldOutRec, OutRec* NewOutRec);
|
||||||
|
#ifdef use_xyz
|
||||||
|
void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2);
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class ClipperOffset
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25);
|
||||||
|
~ClipperOffset();
|
||||||
|
void AddPath(const Path& path, JoinType joinType, EndType endType);
|
||||||
|
void AddPaths(const Paths& paths, JoinType joinType, EndType endType);
|
||||||
|
void Execute(Paths& solution, double delta);
|
||||||
|
void Execute(PolyTree& solution, double delta);
|
||||||
|
void Clear();
|
||||||
|
double MiterLimit;
|
||||||
|
double ArcTolerance;
|
||||||
|
private:
|
||||||
|
Paths m_destPolys;
|
||||||
|
Path m_srcPoly;
|
||||||
|
Path m_destPoly;
|
||||||
|
std::vector<DoublePoint> m_normals;
|
||||||
|
double m_delta, m_sinA, m_sin, m_cos;
|
||||||
|
double m_miterLim, m_StepsPerRad;
|
||||||
|
IntPoint m_lowest;
|
||||||
|
PolyNode m_polyNodes;
|
||||||
|
|
||||||
|
void FixOrientations();
|
||||||
|
void DoOffset(double delta);
|
||||||
|
void OffsetPoint(int j, int& k, JoinType jointype);
|
||||||
|
void DoSquare(int j, int k);
|
||||||
|
void DoMiter(int j, int k, double r);
|
||||||
|
void DoRound(int j, int k);
|
||||||
|
};
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
class clipperException : public std::exception
|
class clipperException : public std::exception
|
||||||
|
|
|
@ -21,7 +21,7 @@ endif()
|
||||||
|
|
||||||
set(draco_root "${CMAKE_CURRENT_SOURCE_DIR}")
|
set(draco_root "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
set(draco_src_root "${draco_root}/src/draco")
|
set(draco_src_root "${draco_root}/src/draco")
|
||||||
set(draco_build "${CMAKE_BINARY_DIR}")
|
set(draco_build "${Assimp_BINARY_DIR}")
|
||||||
|
|
||||||
if("${draco_root}" STREQUAL "${draco_build}")
|
if("${draco_root}" STREQUAL "${draco_build}")
|
||||||
message(
|
message(
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Run manually to reformat a file:
|
||||||
|
# clang-format -i --style=file <file>
|
||||||
|
Language: Cpp
|
||||||
|
BasedOnStyle: Google
|
|
@ -0,0 +1,53 @@
|
||||||
|
name: Bug Report
|
||||||
|
description: Let us know that something does not work as expected.
|
||||||
|
title: "[Bug]: Please title this bug report"
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: Describe the issue
|
||||||
|
description: What happened, and what did you expect to happen?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: steps
|
||||||
|
attributes:
|
||||||
|
label: Steps to reproduce the problem
|
||||||
|
description: It is important that we are able to reproduce the problem that you are experiencing. Please provide all code and relevant steps to reproduce the problem, including your `BUILD`/`CMakeLists.txt` file and build commands. Links to a GitHub branch or [godbolt.org](https://godbolt.org/) that demonstrate the problem are also helpful.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: What version of GoogleTest are you using?
|
||||||
|
description: Please include the output of `git rev-parse HEAD` or the GoogleTest release version number that you are using.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: os
|
||||||
|
attributes:
|
||||||
|
label: What operating system and version are you using?
|
||||||
|
description: If you are using a Linux distribution please include the name and version of the distribution as well.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: compiler
|
||||||
|
attributes:
|
||||||
|
label: What compiler and version are you using?
|
||||||
|
description: Please include the output of `gcc -v` or `clang -v`, or the equivalent for your compiler.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: buildsystem
|
||||||
|
attributes:
|
||||||
|
label: What build system are you using?
|
||||||
|
description: Please include the output of `bazel --version` or `cmake --version`, or the equivalent for your build system.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: additional
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Add any other context about the problem here.
|
||||||
|
validations:
|
||||||
|
required: false
|
|
@ -0,0 +1,33 @@
|
||||||
|
name: Feature request
|
||||||
|
description: Propose a new feature.
|
||||||
|
title: "[FR]: Please title this feature request"
|
||||||
|
labels: "enhancement"
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Does the feature exist in the most recent commit?
|
||||||
|
description: We recommend using the latest commit from GitHub in your projects.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: why
|
||||||
|
attributes:
|
||||||
|
label: Why do we need this feature?
|
||||||
|
description: Ideally, explain why a combination of existing features cannot be used instead.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: proposal
|
||||||
|
attributes:
|
||||||
|
label: Describe the proposal.
|
||||||
|
description: Include a detailed description of the feature, with usage examples.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: platform
|
||||||
|
attributes:
|
||||||
|
label: Is the feature specific to an operating system, compiler, or build system version?
|
||||||
|
description: If it is, please specify which versions.
|
||||||
|
validations:
|
||||||
|
required: true
|
|
@ -0,0 +1,5 @@
|
||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Get Help
|
||||||
|
url: https://github.com/google/googletest/discussions
|
||||||
|
about: Please ask and answer questions here.
|
|
@ -0,0 +1,43 @@
|
||||||
|
name: ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
env:
|
||||||
|
BAZEL_CXXOPTS: -std=c++14
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Tests
|
||||||
|
run: bazel test --cxxopt=-std=c++14 --features=external_include_paths --test_output=errors ...
|
||||||
|
|
||||||
|
macOS:
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Tests
|
||||||
|
run: bazel test --cxxopt=-std=c++14 --features=external_include_paths --test_output=errors ...
|
||||||
|
|
||||||
|
|
||||||
|
Windows:
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Tests
|
||||||
|
run: bazel test --cxxopt=/std:c++14 --features=external_include_paths --test_output=errors ...
|
|
@ -0,0 +1,88 @@
|
||||||
|
# Ignore CI build directory
|
||||||
|
build/
|
||||||
|
xcuserdata
|
||||||
|
cmake-build-debug/
|
||||||
|
.idea/
|
||||||
|
bazel-bin
|
||||||
|
bazel-genfiles
|
||||||
|
bazel-googletest
|
||||||
|
bazel-out
|
||||||
|
bazel-testlogs
|
||||||
|
# python
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Visual Studio files
|
||||||
|
.vs
|
||||||
|
*.sdf
|
||||||
|
*.opensdf
|
||||||
|
*.VC.opendb
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
_ReSharper.Caches/
|
||||||
|
Win32-Debug/
|
||||||
|
Win32-Release/
|
||||||
|
x64-Debug/
|
||||||
|
x64-Release/
|
||||||
|
|
||||||
|
# VSCode files
|
||||||
|
.cache/
|
||||||
|
cmake-variants.yaml
|
||||||
|
|
||||||
|
# Ignore autoconf / automake files
|
||||||
|
Makefile.in
|
||||||
|
aclocal.m4
|
||||||
|
configure
|
||||||
|
build-aux/
|
||||||
|
autom4te.cache/
|
||||||
|
googletest/m4/libtool.m4
|
||||||
|
googletest/m4/ltoptions.m4
|
||||||
|
googletest/m4/ltsugar.m4
|
||||||
|
googletest/m4/ltversion.m4
|
||||||
|
googletest/m4/lt~obsolete.m4
|
||||||
|
googlemock/m4
|
||||||
|
|
||||||
|
# Ignore generated directories.
|
||||||
|
googlemock/fused-src/
|
||||||
|
googletest/fused-src/
|
||||||
|
|
||||||
|
# macOS files
|
||||||
|
.DS_Store
|
||||||
|
googletest/.DS_Store
|
||||||
|
googletest/xcode/.DS_Store
|
||||||
|
|
||||||
|
# Ignore cmake generated directories and files.
|
||||||
|
CMakeFiles
|
||||||
|
CTestTestfile.cmake
|
||||||
|
Makefile
|
||||||
|
cmake_install.cmake
|
||||||
|
googlemock/CMakeFiles
|
||||||
|
googlemock/CTestTestfile.cmake
|
||||||
|
googlemock/Makefile
|
||||||
|
googlemock/cmake_install.cmake
|
||||||
|
googlemock/gtest
|
||||||
|
/bin
|
||||||
|
/googlemock/gmock.dir
|
||||||
|
/googlemock/gmock_main.dir
|
||||||
|
/googlemock/RUN_TESTS.vcxproj.filters
|
||||||
|
/googlemock/RUN_TESTS.vcxproj
|
||||||
|
/googlemock/INSTALL.vcxproj.filters
|
||||||
|
/googlemock/INSTALL.vcxproj
|
||||||
|
/googlemock/gmock_main.vcxproj.filters
|
||||||
|
/googlemock/gmock_main.vcxproj
|
||||||
|
/googlemock/gmock.vcxproj.filters
|
||||||
|
/googlemock/gmock.vcxproj
|
||||||
|
/googlemock/gmock.sln
|
||||||
|
/googlemock/ALL_BUILD.vcxproj.filters
|
||||||
|
/googlemock/ALL_BUILD.vcxproj
|
||||||
|
/lib
|
||||||
|
/Win32
|
||||||
|
/ZERO_CHECK.vcxproj.filters
|
||||||
|
/ZERO_CHECK.vcxproj
|
||||||
|
/RUN_TESTS.vcxproj.filters
|
||||||
|
/RUN_TESTS.vcxproj
|
||||||
|
/INSTALL.vcxproj.filters
|
||||||
|
/INSTALL.vcxproj
|
||||||
|
/googletest-distribution.sln
|
||||||
|
/CMakeCache.txt
|
||||||
|
/ALL_BUILD.vcxproj.filters
|
||||||
|
/ALL_BUILD.vcxproj
|
|
@ -0,0 +1,219 @@
|
||||||
|
# Copyright 2017 Google Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are
|
||||||
|
# met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above
|
||||||
|
# copyright notice, this list of conditions and the following disclaimer
|
||||||
|
# in the documentation and/or other materials provided with the
|
||||||
|
# distribution.
|
||||||
|
# * Neither the name of Google Inc. nor the names of its
|
||||||
|
# contributors may be used to endorse or promote products derived from
|
||||||
|
# this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Bazel Build for Google C++ Testing Framework(Google Test)
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
licenses(["notice"])
|
||||||
|
|
||||||
|
exports_files(["LICENSE"])
|
||||||
|
|
||||||
|
config_setting(
|
||||||
|
name = "qnx",
|
||||||
|
constraint_values = ["@platforms//os:qnx"],
|
||||||
|
)
|
||||||
|
|
||||||
|
config_setting(
|
||||||
|
name = "windows",
|
||||||
|
constraint_values = ["@platforms//os:windows"],
|
||||||
|
)
|
||||||
|
|
||||||
|
config_setting(
|
||||||
|
name = "freebsd",
|
||||||
|
constraint_values = ["@platforms//os:freebsd"],
|
||||||
|
)
|
||||||
|
|
||||||
|
config_setting(
|
||||||
|
name = "openbsd",
|
||||||
|
constraint_values = ["@platforms//os:openbsd"],
|
||||||
|
)
|
||||||
|
|
||||||
|
config_setting(
|
||||||
|
name = "msvc_compiler",
|
||||||
|
flag_values = {
|
||||||
|
"@bazel_tools//tools/cpp:compiler": "msvc-cl",
|
||||||
|
},
|
||||||
|
visibility = [":__subpackages__"],
|
||||||
|
)
|
||||||
|
|
||||||
|
config_setting(
|
||||||
|
name = "has_absl",
|
||||||
|
values = {"define": "absl=1"},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Library that defines the FRIEND_TEST macro.
|
||||||
|
cc_library(
|
||||||
|
name = "gtest_prod",
|
||||||
|
hdrs = ["googletest/include/gtest/gtest_prod.h"],
|
||||||
|
includes = ["googletest/include"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Google Test including Google Mock
|
||||||
|
cc_library(
|
||||||
|
name = "gtest",
|
||||||
|
srcs = glob(
|
||||||
|
include = [
|
||||||
|
"googletest/src/*.cc",
|
||||||
|
"googletest/src/*.h",
|
||||||
|
"googletest/include/gtest/**/*.h",
|
||||||
|
"googlemock/src/*.cc",
|
||||||
|
"googlemock/include/gmock/**/*.h",
|
||||||
|
],
|
||||||
|
exclude = [
|
||||||
|
"googletest/src/gtest-all.cc",
|
||||||
|
"googletest/src/gtest_main.cc",
|
||||||
|
"googlemock/src/gmock-all.cc",
|
||||||
|
"googlemock/src/gmock_main.cc",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
hdrs = glob([
|
||||||
|
"googletest/include/gtest/*.h",
|
||||||
|
"googlemock/include/gmock/*.h",
|
||||||
|
]),
|
||||||
|
copts = select({
|
||||||
|
":qnx": [],
|
||||||
|
":windows": [],
|
||||||
|
"//conditions:default": ["-pthread"],
|
||||||
|
}),
|
||||||
|
defines = select({
|
||||||
|
":has_absl": ["GTEST_HAS_ABSL=1"],
|
||||||
|
"//conditions:default": [],
|
||||||
|
}),
|
||||||
|
features = select({
|
||||||
|
":windows": ["windows_export_all_symbols"],
|
||||||
|
"//conditions:default": [],
|
||||||
|
}),
|
||||||
|
includes = [
|
||||||
|
"googlemock",
|
||||||
|
"googlemock/include",
|
||||||
|
"googletest",
|
||||||
|
"googletest/include",
|
||||||
|
],
|
||||||
|
linkopts = select({
|
||||||
|
":qnx": ["-lregex"],
|
||||||
|
":windows": [],
|
||||||
|
":freebsd": [
|
||||||
|
"-lm",
|
||||||
|
"-pthread",
|
||||||
|
],
|
||||||
|
":openbsd": [
|
||||||
|
"-lm",
|
||||||
|
"-pthread",
|
||||||
|
],
|
||||||
|
"//conditions:default": ["-pthread"],
|
||||||
|
}),
|
||||||
|
deps = select({
|
||||||
|
":has_absl": [
|
||||||
|
"@com_google_absl//absl/container:flat_hash_set",
|
||||||
|
"@com_google_absl//absl/debugging:failure_signal_handler",
|
||||||
|
"@com_google_absl//absl/debugging:stacktrace",
|
||||||
|
"@com_google_absl//absl/debugging:symbolize",
|
||||||
|
"@com_google_absl//absl/flags:flag",
|
||||||
|
"@com_google_absl//absl/flags:parse",
|
||||||
|
"@com_google_absl//absl/flags:reflection",
|
||||||
|
"@com_google_absl//absl/flags:usage",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_absl//absl/types:any",
|
||||||
|
"@com_google_absl//absl/types:optional",
|
||||||
|
"@com_google_absl//absl/types:variant",
|
||||||
|
"@com_googlesource_code_re2//:re2",
|
||||||
|
],
|
||||||
|
"//conditions:default": [],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "gtest_main",
|
||||||
|
srcs = ["googlemock/src/gmock_main.cc"],
|
||||||
|
features = select({
|
||||||
|
":windows": ["windows_export_all_symbols"],
|
||||||
|
"//conditions:default": [],
|
||||||
|
}),
|
||||||
|
deps = [":gtest"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# The following rules build samples of how to use gTest.
|
||||||
|
cc_library(
|
||||||
|
name = "gtest_sample_lib",
|
||||||
|
srcs = [
|
||||||
|
"googletest/samples/sample1.cc",
|
||||||
|
"googletest/samples/sample2.cc",
|
||||||
|
"googletest/samples/sample4.cc",
|
||||||
|
],
|
||||||
|
hdrs = [
|
||||||
|
"googletest/samples/prime_tables.h",
|
||||||
|
"googletest/samples/sample1.h",
|
||||||
|
"googletest/samples/sample2.h",
|
||||||
|
"googletest/samples/sample3-inl.h",
|
||||||
|
"googletest/samples/sample4.h",
|
||||||
|
],
|
||||||
|
features = select({
|
||||||
|
":windows": ["windows_export_all_symbols"],
|
||||||
|
"//conditions:default": [],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "gtest_samples",
|
||||||
|
size = "small",
|
||||||
|
# All Samples except:
|
||||||
|
# sample9 (main)
|
||||||
|
# sample10 (main and takes a command line option and needs to be separate)
|
||||||
|
srcs = [
|
||||||
|
"googletest/samples/sample1_unittest.cc",
|
||||||
|
"googletest/samples/sample2_unittest.cc",
|
||||||
|
"googletest/samples/sample3_unittest.cc",
|
||||||
|
"googletest/samples/sample4_unittest.cc",
|
||||||
|
"googletest/samples/sample5_unittest.cc",
|
||||||
|
"googletest/samples/sample6_unittest.cc",
|
||||||
|
"googletest/samples/sample7_unittest.cc",
|
||||||
|
"googletest/samples/sample8_unittest.cc",
|
||||||
|
],
|
||||||
|
linkstatic = 0,
|
||||||
|
deps = [
|
||||||
|
"gtest_sample_lib",
|
||||||
|
":gtest_main",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "sample9_unittest",
|
||||||
|
size = "small",
|
||||||
|
srcs = ["googletest/samples/sample9_unittest.cc"],
|
||||||
|
deps = [":gtest"],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "sample10_unittest",
|
||||||
|
size = "small",
|
||||||
|
srcs = ["googletest/samples/sample10_unittest.cc"],
|
||||||
|
deps = [":gtest"],
|
||||||
|
)
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Note: CMake support is community-based. The maintainers do not use CMake
|
||||||
|
# internally.
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
|
project(googletest-distribution)
|
||||||
|
set(GOOGLETEST_VERSION 1.14.0)
|
||||||
|
|
||||||
|
if(NOT CYGWIN AND NOT MSYS AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL QNX)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
include(CMakeDependentOption)
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
#Note that googlemock target already builds googletest
|
||||||
|
option(BUILD_GMOCK "Builds the googlemock subproject" ON)
|
||||||
|
option(INSTALL_GTEST "Enable installation of googletest. (Projects embedding googletest may want to turn this OFF.)" ON)
|
||||||
|
option(GTEST_HAS_ABSL "Use Abseil and RE2. Requires Abseil and RE2 to be separately added to the build." OFF)
|
||||||
|
|
||||||
|
if(BUILD_GMOCK)
|
||||||
|
add_subdirectory( googlemock )
|
||||||
|
else()
|
||||||
|
add_subdirectory( googletest )
|
||||||
|
endif()
|
|
@ -0,0 +1,141 @@
|
||||||
|
# How to become a contributor and submit your own code
|
||||||
|
|
||||||
|
## Contributor License Agreements
|
||||||
|
|
||||||
|
We'd love to accept your patches! Before we can take them, we have to jump a
|
||||||
|
couple of legal hurdles.
|
||||||
|
|
||||||
|
Please fill out either the individual or corporate Contributor License Agreement
|
||||||
|
(CLA).
|
||||||
|
|
||||||
|
* If you are an individual writing original source code and you're sure you
|
||||||
|
own the intellectual property, then you'll need to sign an
|
||||||
|
[individual CLA](https://developers.google.com/open-source/cla/individual).
|
||||||
|
* If you work for a company that wants to allow you to contribute your work,
|
||||||
|
then you'll need to sign a
|
||||||
|
[corporate CLA](https://developers.google.com/open-source/cla/corporate).
|
||||||
|
|
||||||
|
Follow either of the two links above to access the appropriate CLA and
|
||||||
|
instructions for how to sign and return it. Once we receive it, we'll be able to
|
||||||
|
accept your pull requests.
|
||||||
|
|
||||||
|
## Are you a Googler?
|
||||||
|
|
||||||
|
If you are a Googler, please make an attempt to submit an internal contribution
|
||||||
|
rather than a GitHub Pull Request. If you are not able to submit internally, a
|
||||||
|
PR is acceptable as an alternative.
|
||||||
|
|
||||||
|
## Contributing A Patch
|
||||||
|
|
||||||
|
1. Submit an issue describing your proposed change to the
|
||||||
|
[issue tracker](https://github.com/google/googletest/issues).
|
||||||
|
2. Please don't mix more than one logical change per submittal, because it
|
||||||
|
makes the history hard to follow. If you want to make a change that doesn't
|
||||||
|
have a corresponding issue in the issue tracker, please create one.
|
||||||
|
3. Also, coordinate with team members that are listed on the issue in question.
|
||||||
|
This ensures that work isn't being duplicated and communicating your plan
|
||||||
|
early also generally leads to better patches.
|
||||||
|
4. If your proposed change is accepted, and you haven't already done so, sign a
|
||||||
|
Contributor License Agreement
|
||||||
|
([see details above](#contributor-license-agreements)).
|
||||||
|
5. Fork the desired repo, develop and test your code changes.
|
||||||
|
6. Ensure that your code adheres to the existing style in the sample to which
|
||||||
|
you are contributing.
|
||||||
|
7. Ensure that your code has an appropriate set of unit tests which all pass.
|
||||||
|
8. Submit a pull request.
|
||||||
|
|
||||||
|
## The Google Test and Google Mock Communities
|
||||||
|
|
||||||
|
The Google Test community exists primarily through the
|
||||||
|
[discussion group](http://groups.google.com/group/googletestframework) and the
|
||||||
|
GitHub repository. Likewise, the Google Mock community exists primarily through
|
||||||
|
their own [discussion group](http://groups.google.com/group/googlemock). You are
|
||||||
|
definitely encouraged to contribute to the discussion and you can also help us
|
||||||
|
to keep the effectiveness of the group high by following and promoting the
|
||||||
|
guidelines listed here.
|
||||||
|
|
||||||
|
### Please Be Friendly
|
||||||
|
|
||||||
|
Showing courtesy and respect to others is a vital part of the Google culture,
|
||||||
|
and we strongly encourage everyone participating in Google Test development to
|
||||||
|
join us in accepting nothing less. Of course, being courteous is not the same as
|
||||||
|
failing to constructively disagree with each other, but it does mean that we
|
||||||
|
should be respectful of each other when enumerating the 42 technical reasons
|
||||||
|
that a particular proposal may not be the best choice. There's never a reason to
|
||||||
|
be antagonistic or dismissive toward anyone who is sincerely trying to
|
||||||
|
contribute to a discussion.
|
||||||
|
|
||||||
|
Sure, C++ testing is serious business and all that, but it's also a lot of fun.
|
||||||
|
Let's keep it that way. Let's strive to be one of the friendliest communities in
|
||||||
|
all of open source.
|
||||||
|
|
||||||
|
As always, discuss Google Test in the official GoogleTest discussion group. You
|
||||||
|
don't have to actually submit code in order to sign up. Your participation
|
||||||
|
itself is a valuable contribution.
|
||||||
|
|
||||||
|
## Style
|
||||||
|
|
||||||
|
To keep the source consistent, readable, diffable and easy to merge, we use a
|
||||||
|
fairly rigid coding style, as defined by the
|
||||||
|
[google-styleguide](https://github.com/google/styleguide) project. All patches
|
||||||
|
will be expected to conform to the style outlined
|
||||||
|
[here](https://google.github.io/styleguide/cppguide.html). Use
|
||||||
|
[.clang-format](https://github.com/google/googletest/blob/main/.clang-format) to
|
||||||
|
check your formatting.
|
||||||
|
|
||||||
|
## Requirements for Contributors
|
||||||
|
|
||||||
|
If you plan to contribute a patch, you need to build Google Test, Google Mock,
|
||||||
|
and their own tests from a git checkout, which has further requirements:
|
||||||
|
|
||||||
|
* [Python](https://www.python.org/) v3.6 or newer (for running some of the
|
||||||
|
tests and re-generating certain source files from templates)
|
||||||
|
* [CMake](https://cmake.org/) v2.8.12 or newer
|
||||||
|
|
||||||
|
## Developing Google Test and Google Mock
|
||||||
|
|
||||||
|
This section discusses how to make your own changes to the Google Test project.
|
||||||
|
|
||||||
|
### Testing Google Test and Google Mock Themselves
|
||||||
|
|
||||||
|
To make sure your changes work as intended and don't break existing
|
||||||
|
functionality, you'll want to compile and run Google Test and GoogleMock's own
|
||||||
|
tests. For that you can use CMake:
|
||||||
|
|
||||||
|
```
|
||||||
|
mkdir mybuild
|
||||||
|
cd mybuild
|
||||||
|
cmake -Dgtest_build_tests=ON -Dgmock_build_tests=ON ${GTEST_REPO_DIR}
|
||||||
|
```
|
||||||
|
|
||||||
|
To choose between building only Google Test or Google Mock, you may modify your
|
||||||
|
cmake command to be one of each
|
||||||
|
|
||||||
|
```
|
||||||
|
cmake -Dgtest_build_tests=ON ${GTEST_DIR} # sets up Google Test tests
|
||||||
|
cmake -Dgmock_build_tests=ON ${GMOCK_DIR} # sets up Google Mock tests
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure you have Python installed, as some of Google Test's tests are written
|
||||||
|
in Python. If the cmake command complains about not being able to find Python
|
||||||
|
(`Could NOT find PythonInterp (missing: PYTHON_EXECUTABLE)`), try telling it
|
||||||
|
explicitly where your Python executable can be found:
|
||||||
|
|
||||||
|
```
|
||||||
|
cmake -DPYTHON_EXECUTABLE=path/to/python ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, you can build Google Test and / or Google Mock and all desired tests. On
|
||||||
|
\*nix, this is usually done by
|
||||||
|
|
||||||
|
```
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
To run the tests, do
|
||||||
|
|
||||||
|
```
|
||||||
|
make test
|
||||||
|
```
|
||||||
|
|
||||||
|
All tests should pass.
|
|
@ -5,33 +5,61 @@
|
||||||
|
|
||||||
Ajay Joshi <jaj@google.com>
|
Ajay Joshi <jaj@google.com>
|
||||||
Balázs Dán <balazs.dan@gmail.com>
|
Balázs Dán <balazs.dan@gmail.com>
|
||||||
|
Benoit Sigoure <tsuna@google.com>
|
||||||
Bharat Mediratta <bharat@menalto.com>
|
Bharat Mediratta <bharat@menalto.com>
|
||||||
|
Bogdan Piloca <boo@google.com>
|
||||||
Chandler Carruth <chandlerc@google.com>
|
Chandler Carruth <chandlerc@google.com>
|
||||||
Chris Prince <cprince@google.com>
|
Chris Prince <cprince@google.com>
|
||||||
Chris Taylor <taylorc@google.com>
|
Chris Taylor <taylorc@google.com>
|
||||||
Dan Egnor <egnor@google.com>
|
Dan Egnor <egnor@google.com>
|
||||||
|
Dave MacLachlan <dmaclach@gmail.com>
|
||||||
|
David Anderson <danderson@google.com>
|
||||||
|
Dean Sturtevant
|
||||||
Eric Roman <eroman@chromium.org>
|
Eric Roman <eroman@chromium.org>
|
||||||
|
Gene Volovich <gv@cite.com>
|
||||||
Hady Zalek <hady.zalek@gmail.com>
|
Hady Zalek <hady.zalek@gmail.com>
|
||||||
|
Hal Burch <gmock@hburch.com>
|
||||||
Jeffrey Yasskin <jyasskin@google.com>
|
Jeffrey Yasskin <jyasskin@google.com>
|
||||||
|
Jim Keller <jimkeller@google.com>
|
||||||
|
Joe Walnes <joe@truemesh.com>
|
||||||
|
Jon Wray <jwray@google.com>
|
||||||
Jói Sigurðsson <joi@google.com>
|
Jói Sigurðsson <joi@google.com>
|
||||||
Keir Mierle <mierle@gmail.com>
|
Keir Mierle <mierle@gmail.com>
|
||||||
Keith Ray <keith.ray@gmail.com>
|
Keith Ray <keith.ray@gmail.com>
|
||||||
Kenton Varda <kenton@google.com>
|
Kenton Varda <kenton@google.com>
|
||||||
|
Kostya Serebryany <kcc@google.com>
|
||||||
|
Krystian Kuzniarek <krystian.kuzniarek@gmail.com>
|
||||||
|
Lev Makhlis
|
||||||
Manuel Klimek <klimek@google.com>
|
Manuel Klimek <klimek@google.com>
|
||||||
|
Mario Tanev <radix@google.com>
|
||||||
|
Mark Paskin
|
||||||
Markus Heule <markus.heule@gmail.com>
|
Markus Heule <markus.heule@gmail.com>
|
||||||
|
Martijn Vels <mvels@google.com>
|
||||||
|
Matthew Simmons <simmonmt@acm.org>
|
||||||
Mika Raento <mikie@iki.fi>
|
Mika Raento <mikie@iki.fi>
|
||||||
|
Mike Bland <mbland@google.com>
|
||||||
Miklós Fazekas <mfazekas@szemafor.com>
|
Miklós Fazekas <mfazekas@szemafor.com>
|
||||||
|
Neal Norwitz <nnorwitz@gmail.com>
|
||||||
|
Nermin Ozkiranartli <nermin@google.com>
|
||||||
|
Owen Carlsen <ocarlsen@google.com>
|
||||||
|
Paneendra Ba <paneendra@google.com>
|
||||||
Pasi Valminen <pasi.valminen@gmail.com>
|
Pasi Valminen <pasi.valminen@gmail.com>
|
||||||
Patrick Hanna <phanna@google.com>
|
Patrick Hanna <phanna@google.com>
|
||||||
Patrick Riley <pfr@google.com>
|
Patrick Riley <pfr@google.com>
|
||||||
|
Paul Menage <menage@google.com>
|
||||||
Peter Kaminski <piotrk@google.com>
|
Peter Kaminski <piotrk@google.com>
|
||||||
|
Piotr Kaminski <piotrk@google.com>
|
||||||
Preston Jackson <preston.a.jackson@gmail.com>
|
Preston Jackson <preston.a.jackson@gmail.com>
|
||||||
Rainer Klaffenboeck <rainer.klaffenboeck@dynatrace.com>
|
Rainer Klaffenboeck <rainer.klaffenboeck@dynatrace.com>
|
||||||
Russ Cox <rsc@google.com>
|
Russ Cox <rsc@google.com>
|
||||||
Russ Rufer <russ@pentad.com>
|
Russ Rufer <russ@pentad.com>
|
||||||
Sean Mcafee <eefacm@gmail.com>
|
Sean Mcafee <eefacm@gmail.com>
|
||||||
Sigurður Ásgeirsson <siggi@google.com>
|
Sigurður Ásgeirsson <siggi@google.com>
|
||||||
|
Sverre Sundsdal <sundsdal@gmail.com>
|
||||||
|
Szymon Sobik <sobik.szymon@gmail.com>
|
||||||
|
Takeshi Yoshino <tyoshino@google.com>
|
||||||
Tracy Bialik <tracy@pentad.com>
|
Tracy Bialik <tracy@pentad.com>
|
||||||
Vadim Berman <vadimb@google.com>
|
Vadim Berman <vadimb@google.com>
|
||||||
Vlad Losev <vladl@google.com>
|
Vlad Losev <vladl@google.com>
|
||||||
|
Wolfgang Klier <wklier@google.com>
|
||||||
Zhanyong Wan <wan@google.com>
|
Zhanyong Wan <wan@google.com>
|
|
@ -0,0 +1,146 @@
|
||||||
|
# GoogleTest
|
||||||
|
|
||||||
|
### Announcements
|
||||||
|
|
||||||
|
#### Live at Head
|
||||||
|
|
||||||
|
GoogleTest now follows the
|
||||||
|
[Abseil Live at Head philosophy](https://abseil.io/about/philosophy#upgrade-support).
|
||||||
|
We recommend
|
||||||
|
[updating to the latest commit in the `main` branch as often as possible](https://github.com/abseil/abseil-cpp/blob/master/FAQ.md#what-is-live-at-head-and-how-do-i-do-it).
|
||||||
|
We do publish occasional semantic versions, tagged with
|
||||||
|
`v${major}.${minor}.${patch}` (e.g. `v1.13.0`).
|
||||||
|
|
||||||
|
#### Documentation Updates
|
||||||
|
|
||||||
|
Our documentation is now live on GitHub Pages at
|
||||||
|
https://google.github.io/googletest/. We recommend browsing the documentation on
|
||||||
|
GitHub Pages rather than directly in the repository.
|
||||||
|
|
||||||
|
#### Release 1.13.0
|
||||||
|
|
||||||
|
[Release 1.13.0](https://github.com/google/googletest/releases/tag/v1.13.0) is
|
||||||
|
now available.
|
||||||
|
|
||||||
|
The 1.13.x branch requires at least C++14.
|
||||||
|
|
||||||
|
#### Continuous Integration
|
||||||
|
|
||||||
|
We use Google's internal systems for continuous integration. \
|
||||||
|
GitHub Actions were added for the convenience of open-source contributors. They
|
||||||
|
are exclusively maintained by the open-source community and not used by the
|
||||||
|
GoogleTest team.
|
||||||
|
|
||||||
|
#### Coming Soon
|
||||||
|
|
||||||
|
* We are planning to take a dependency on
|
||||||
|
[Abseil](https://github.com/abseil/abseil-cpp).
|
||||||
|
* More documentation improvements are planned.
|
||||||
|
|
||||||
|
## Welcome to **GoogleTest**, Google's C++ test framework!
|
||||||
|
|
||||||
|
This repository is a merger of the formerly separate GoogleTest and GoogleMock
|
||||||
|
projects. These were so closely related that it makes sense to maintain and
|
||||||
|
release them together.
|
||||||
|
|
||||||
|
### Getting Started
|
||||||
|
|
||||||
|
See the [GoogleTest User's Guide](https://google.github.io/googletest/) for
|
||||||
|
documentation. We recommend starting with the
|
||||||
|
[GoogleTest Primer](https://google.github.io/googletest/primer.html).
|
||||||
|
|
||||||
|
More information about building GoogleTest can be found at
|
||||||
|
[googletest/README.md](googletest/README.md).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* xUnit test framework: \
|
||||||
|
Googletest is based on the [xUnit](https://en.wikipedia.org/wiki/XUnit)
|
||||||
|
testing framework, a popular architecture for unit testing
|
||||||
|
* Test discovery: \
|
||||||
|
Googletest automatically discovers and runs your tests, eliminating the need
|
||||||
|
to manually register your tests
|
||||||
|
* Rich set of assertions: \
|
||||||
|
Googletest provides a variety of assertions, such as equality, inequality,
|
||||||
|
exceptions, and more, making it easy to test your code
|
||||||
|
* User-defined assertions: \
|
||||||
|
You can define your own assertions with Googletest, making it simple to
|
||||||
|
write tests that are specific to your code
|
||||||
|
* Death tests: \
|
||||||
|
Googletest supports death tests, which verify that your code exits in a
|
||||||
|
certain way, making it useful for testing error-handling code
|
||||||
|
* Fatal and non-fatal failures: \
|
||||||
|
You can specify whether a test failure should be treated as fatal or
|
||||||
|
non-fatal with Googletest, allowing tests to continue running even if a
|
||||||
|
failure occurs
|
||||||
|
* Value-parameterized tests: \
|
||||||
|
Googletest supports value-parameterized tests, which run multiple times with
|
||||||
|
different input values, making it useful for testing functions that take
|
||||||
|
different inputs
|
||||||
|
* Type-parameterized tests: \
|
||||||
|
Googletest also supports type-parameterized tests, which run with different
|
||||||
|
data types, making it useful for testing functions that work with different
|
||||||
|
data types
|
||||||
|
* Various options for running tests: \
|
||||||
|
Googletest provides many options for running tests including running
|
||||||
|
individual tests, running tests in a specific order and running tests in
|
||||||
|
parallel
|
||||||
|
|
||||||
|
## Supported Platforms
|
||||||
|
|
||||||
|
GoogleTest follows Google's
|
||||||
|
[Foundational C++ Support Policy](https://opensource.google/documentation/policies/cplusplus-support).
|
||||||
|
See
|
||||||
|
[this table](https://github.com/google/oss-policies-info/blob/main/foundational-cxx-support-matrix.md)
|
||||||
|
for a list of currently supported versions of compilers, platforms, and build
|
||||||
|
tools.
|
||||||
|
|
||||||
|
## Who Is Using GoogleTest?
|
||||||
|
|
||||||
|
In addition to many internal projects at Google, GoogleTest is also used by the
|
||||||
|
following notable projects:
|
||||||
|
|
||||||
|
* The [Chromium projects](http://www.chromium.org/) (behind the Chrome browser
|
||||||
|
and Chrome OS).
|
||||||
|
* The [LLVM](http://llvm.org/) compiler.
|
||||||
|
* [Protocol Buffers](https://github.com/google/protobuf), Google's data
|
||||||
|
interchange format.
|
||||||
|
* The [OpenCV](http://opencv.org/) computer vision library.
|
||||||
|
|
||||||
|
## Related Open Source Projects
|
||||||
|
|
||||||
|
[GTest Runner](https://github.com/nholthaus/gtest-runner) is a Qt5 based
|
||||||
|
automated test-runner and Graphical User Interface with powerful features for
|
||||||
|
Windows and Linux platforms.
|
||||||
|
|
||||||
|
[GoogleTest UI](https://github.com/ospector/gtest-gbar) is a test runner that
|
||||||
|
runs your test binary, allows you to track its progress via a progress bar, and
|
||||||
|
displays a list of test failures. Clicking on one shows failure text. GoogleTest
|
||||||
|
UI is written in C#.
|
||||||
|
|
||||||
|
[GTest TAP Listener](https://github.com/kinow/gtest-tap-listener) is an event
|
||||||
|
listener for GoogleTest that implements the
|
||||||
|
[TAP protocol](https://en.wikipedia.org/wiki/Test_Anything_Protocol) for test
|
||||||
|
result output. If your test runner understands TAP, you may find it useful.
|
||||||
|
|
||||||
|
[gtest-parallel](https://github.com/google/gtest-parallel) is a test runner that
|
||||||
|
runs tests from your binary in parallel to provide significant speed-up.
|
||||||
|
|
||||||
|
[GoogleTest Adapter](https://marketplace.visualstudio.com/items?itemName=DavidSchuldenfrei.gtest-adapter)
|
||||||
|
is a VS Code extension allowing to view GoogleTest in a tree view and run/debug
|
||||||
|
your tests.
|
||||||
|
|
||||||
|
[C++ TestMate](https://github.com/matepek/vscode-catch2-test-adapter) is a VS
|
||||||
|
Code extension allowing to view GoogleTest in a tree view and run/debug your
|
||||||
|
tests.
|
||||||
|
|
||||||
|
[Cornichon](https://pypi.org/project/cornichon/) is a small Gherkin DSL parser
|
||||||
|
that generates stub code for GoogleTest.
|
||||||
|
|
||||||
|
## Contributing Changes
|
||||||
|
|
||||||
|
Please read
|
||||||
|
[`CONTRIBUTING.md`](https://github.com/google/googletest/blob/main/CONTRIBUTING.md)
|
||||||
|
for details on how to contribute to this project.
|
||||||
|
|
||||||
|
Happy testing!
|
|
@ -0,0 +1,27 @@
|
||||||
|
workspace(name = "com_google_googletest")
|
||||||
|
|
||||||
|
load("//:googletest_deps.bzl", "googletest_deps")
|
||||||
|
googletest_deps()
|
||||||
|
|
||||||
|
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||||
|
|
||||||
|
http_archive(
|
||||||
|
name = "rules_python", # 2023-07-31T20:39:27Z
|
||||||
|
sha256 = "1250b59a33c591a1c4ba68c62e95fc88a84c334ec35a2e23f46cbc1b9a5a8b55",
|
||||||
|
strip_prefix = "rules_python-e355becc30275939d87116a4ec83dad4bb50d9e1",
|
||||||
|
urls = ["https://github.com/bazelbuild/rules_python/archive/e355becc30275939d87116a4ec83dad4bb50d9e1.zip"],
|
||||||
|
)
|
||||||
|
|
||||||
|
http_archive(
|
||||||
|
name = "bazel_skylib", # 2023-05-31T19:24:07Z
|
||||||
|
sha256 = "08c0386f45821ce246bbbf77503c973246ed6ee5c3463e41efc197fa9bc3a7f4",
|
||||||
|
strip_prefix = "bazel-skylib-288731ef9f7f688932bd50e704a91a45ec185f9b",
|
||||||
|
urls = ["https://github.com/bazelbuild/bazel-skylib/archive/288731ef9f7f688932bd50e704a91a45ec185f9b.zip"],
|
||||||
|
)
|
||||||
|
|
||||||
|
http_archive(
|
||||||
|
name = "platforms", # 2023-07-28T19:44:27Z
|
||||||
|
sha256 = "40eb313613ff00a5c03eed20aba58890046f4d38dec7344f00bb9a8867853526",
|
||||||
|
strip_prefix = "platforms-4ad40ef271da8176d4fc0194d2089b8a76e19d7b",
|
||||||
|
urls = ["https://github.com/bazelbuild/platforms/archive/4ad40ef271da8176d4fc0194d2089b8a76e19d7b.zip"],
|
||||||
|
)
|
|
@ -0,0 +1,137 @@
|
||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Copyright 2020, Google Inc.
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are
|
||||||
|
# met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above
|
||||||
|
# copyright notice, this list of conditions and the following disclaimer
|
||||||
|
# in the documentation and/or other materials provided with the
|
||||||
|
# distribution.
|
||||||
|
# * Neither the name of Google Inc. nor the names of its
|
||||||
|
# contributors may be used to endorse or promote products derived from
|
||||||
|
# this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
set -euox pipefail
|
||||||
|
|
||||||
|
readonly LINUX_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20230217"
|
||||||
|
readonly LINUX_GCC_FLOOR_CONTAINER="gcr.io/google.com/absl-177019/linux_gcc-floor:20230120"
|
||||||
|
|
||||||
|
if [[ -z ${GTEST_ROOT:-} ]]; then
|
||||||
|
GTEST_ROOT="$(realpath $(dirname ${0})/..)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z ${STD:-} ]]; then
|
||||||
|
STD="c++14 c++17 c++20"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test the CMake build
|
||||||
|
for cc in /usr/local/bin/gcc /opt/llvm/clang/bin/clang; do
|
||||||
|
for cmake_off_on in OFF ON; do
|
||||||
|
time docker run \
|
||||||
|
--volume="${GTEST_ROOT}:/src:ro" \
|
||||||
|
--tmpfs="/build:exec" \
|
||||||
|
--workdir="/build" \
|
||||||
|
--rm \
|
||||||
|
--env="CC=${cc}" \
|
||||||
|
--env=CXXFLAGS="-Werror -Wdeprecated" \
|
||||||
|
${LINUX_LATEST_CONTAINER} \
|
||||||
|
/bin/bash -c "
|
||||||
|
cmake /src \
|
||||||
|
-DCMAKE_CXX_STANDARD=14 \
|
||||||
|
-Dgtest_build_samples=ON \
|
||||||
|
-Dgtest_build_tests=ON \
|
||||||
|
-Dgmock_build_tests=ON \
|
||||||
|
-Dcxx_no_exception=${cmake_off_on} \
|
||||||
|
-Dcxx_no_rtti=${cmake_off_on} && \
|
||||||
|
make -j$(nproc) && \
|
||||||
|
ctest -j$(nproc) --output-on-failure"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
# Do one test with an older version of GCC
|
||||||
|
time docker run \
|
||||||
|
--volume="${GTEST_ROOT}:/src:ro" \
|
||||||
|
--workdir="/src" \
|
||||||
|
--rm \
|
||||||
|
--env="CC=/usr/local/bin/gcc" \
|
||||||
|
--env="BAZEL_CXXOPTS=-std=c++14" \
|
||||||
|
${LINUX_GCC_FLOOR_CONTAINER} \
|
||||||
|
/usr/local/bin/bazel test ... \
|
||||||
|
--copt="-Wall" \
|
||||||
|
--copt="-Werror" \
|
||||||
|
--copt="-Wuninitialized" \
|
||||||
|
--copt="-Wundef" \
|
||||||
|
--copt="-Wno-error=pragmas" \
|
||||||
|
--distdir="/bazel-distdir" \
|
||||||
|
--features=external_include_paths \
|
||||||
|
--keep_going \
|
||||||
|
--show_timestamps \
|
||||||
|
--test_output=errors
|
||||||
|
|
||||||
|
# Test GCC
|
||||||
|
for std in ${STD}; do
|
||||||
|
for absl in 0 1; do
|
||||||
|
time docker run \
|
||||||
|
--volume="${GTEST_ROOT}:/src:ro" \
|
||||||
|
--workdir="/src" \
|
||||||
|
--rm \
|
||||||
|
--env="CC=/usr/local/bin/gcc" \
|
||||||
|
--env="BAZEL_CXXOPTS=-std=${std}" \
|
||||||
|
${LINUX_LATEST_CONTAINER} \
|
||||||
|
/usr/local/bin/bazel test ... \
|
||||||
|
--copt="-Wall" \
|
||||||
|
--copt="-Werror" \
|
||||||
|
--copt="-Wuninitialized" \
|
||||||
|
--copt="-Wundef" \
|
||||||
|
--define="absl=${absl}" \
|
||||||
|
--distdir="/bazel-distdir" \
|
||||||
|
--features=external_include_paths \
|
||||||
|
--keep_going \
|
||||||
|
--show_timestamps \
|
||||||
|
--test_output=errors
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
# Test Clang
|
||||||
|
for std in ${STD}; do
|
||||||
|
for absl in 0 1; do
|
||||||
|
time docker run \
|
||||||
|
--volume="${GTEST_ROOT}:/src:ro" \
|
||||||
|
--workdir="/src" \
|
||||||
|
--rm \
|
||||||
|
--env="CC=/opt/llvm/clang/bin/clang" \
|
||||||
|
--env="BAZEL_CXXOPTS=-std=${std}" \
|
||||||
|
${LINUX_LATEST_CONTAINER} \
|
||||||
|
/usr/local/bin/bazel test ... \
|
||||||
|
--copt="--gcc-toolchain=/usr/local" \
|
||||||
|
--copt="-Wall" \
|
||||||
|
--copt="-Werror" \
|
||||||
|
--copt="-Wuninitialized" \
|
||||||
|
--copt="-Wundef" \
|
||||||
|
--define="absl=${absl}" \
|
||||||
|
--distdir="/bazel-distdir" \
|
||||||
|
--features=external_include_paths \
|
||||||
|
--keep_going \
|
||||||
|
--linkopt="--gcc-toolchain=/usr/local" \
|
||||||
|
--show_timestamps \
|
||||||
|
--test_output=errors
|
||||||
|
done
|
||||||
|
done
|
|
@ -0,0 +1,76 @@
|
||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Copyright 2020, Google Inc.
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are
|
||||||
|
# met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above
|
||||||
|
# copyright notice, this list of conditions and the following disclaimer
|
||||||
|
# in the documentation and/or other materials provided with the
|
||||||
|
# distribution.
|
||||||
|
# * Neither the name of Google Inc. nor the names of its
|
||||||
|
# contributors may be used to endorse or promote products derived from
|
||||||
|
# this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
set -euox pipefail
|
||||||
|
|
||||||
|
if [[ -z ${GTEST_ROOT:-} ]]; then
|
||||||
|
GTEST_ROOT="$(realpath $(dirname ${0})/..)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test the CMake build
|
||||||
|
for cmake_off_on in OFF ON; do
|
||||||
|
BUILD_DIR=$(mktemp -d build_dir.XXXXXXXX)
|
||||||
|
cd ${BUILD_DIR}
|
||||||
|
time cmake ${GTEST_ROOT} \
|
||||||
|
-DCMAKE_CXX_STANDARD=14 \
|
||||||
|
-Dgtest_build_samples=ON \
|
||||||
|
-Dgtest_build_tests=ON \
|
||||||
|
-Dgmock_build_tests=ON \
|
||||||
|
-Dcxx_no_exception=${cmake_off_on} \
|
||||||
|
-Dcxx_no_rtti=${cmake_off_on}
|
||||||
|
time make
|
||||||
|
time ctest -j$(nproc) --output-on-failure
|
||||||
|
done
|
||||||
|
|
||||||
|
# Test the Bazel build
|
||||||
|
|
||||||
|
# If we are running on Kokoro, check for a versioned Bazel binary.
|
||||||
|
KOKORO_GFILE_BAZEL_BIN="bazel-5.1.1-darwin-x86_64"
|
||||||
|
if [[ ${KOKORO_GFILE_DIR:-} ]] && [[ -f ${KOKORO_GFILE_DIR}/${KOKORO_GFILE_BAZEL_BIN} ]]; then
|
||||||
|
BAZEL_BIN="${KOKORO_GFILE_DIR}/${KOKORO_GFILE_BAZEL_BIN}"
|
||||||
|
chmod +x ${BAZEL_BIN}
|
||||||
|
else
|
||||||
|
BAZEL_BIN="bazel"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd ${GTEST_ROOT}
|
||||||
|
for absl in 0 1; do
|
||||||
|
${BAZEL_BIN} test ... \
|
||||||
|
--copt="-Wall" \
|
||||||
|
--copt="-Werror" \
|
||||||
|
--copt="-Wundef" \
|
||||||
|
--cxxopt="-std=c++14" \
|
||||||
|
--define="absl=${absl}" \
|
||||||
|
--features=external_include_paths \
|
||||||
|
--keep_going \
|
||||||
|
--show_timestamps \
|
||||||
|
--test_output=errors
|
||||||
|
done
|
|
@ -0,0 +1,58 @@
|
||||||
|
SETLOCAL ENABLEDELAYEDEXPANSION
|
||||||
|
|
||||||
|
SET BAZEL_EXE=%KOKORO_GFILE_DIR%\bazel-5.1.1-windows-x86_64.exe
|
||||||
|
|
||||||
|
SET PATH=C:\Python34;%PATH%
|
||||||
|
SET BAZEL_PYTHON=C:\python34\python.exe
|
||||||
|
SET BAZEL_SH=C:\tools\msys64\usr\bin\bash.exe
|
||||||
|
SET CMAKE_BIN="cmake.exe"
|
||||||
|
SET CTEST_BIN="ctest.exe"
|
||||||
|
SET CTEST_OUTPUT_ON_FAILURE=1
|
||||||
|
SET CMAKE_BUILD_PARALLEL_LEVEL=16
|
||||||
|
SET CTEST_PARALLEL_LEVEL=16
|
||||||
|
|
||||||
|
IF EXIST git\googletest (
|
||||||
|
CD git\googletest
|
||||||
|
) ELSE IF EXIST github\googletest (
|
||||||
|
CD github\googletest
|
||||||
|
)
|
||||||
|
|
||||||
|
IF %errorlevel% neq 0 EXIT /B 1
|
||||||
|
|
||||||
|
:: ----------------------------------------------------------------------------
|
||||||
|
:: CMake
|
||||||
|
MKDIR cmake_msvc2022
|
||||||
|
CD cmake_msvc2022
|
||||||
|
|
||||||
|
%CMAKE_BIN% .. ^
|
||||||
|
-G "Visual Studio 17 2022" ^
|
||||||
|
-DPYTHON_EXECUTABLE:FILEPATH=c:\python37\python.exe ^
|
||||||
|
-DPYTHON_INCLUDE_DIR:PATH=c:\python37\include ^
|
||||||
|
-DPYTHON_LIBRARY:FILEPATH=c:\python37\lib\site-packages\pip ^
|
||||||
|
-Dgtest_build_samples=ON ^
|
||||||
|
-Dgtest_build_tests=ON ^
|
||||||
|
-Dgmock_build_tests=ON
|
||||||
|
IF %errorlevel% neq 0 EXIT /B 1
|
||||||
|
|
||||||
|
%CMAKE_BIN% --build . --target ALL_BUILD --config Debug -- -maxcpucount
|
||||||
|
IF %errorlevel% neq 0 EXIT /B 1
|
||||||
|
|
||||||
|
%CTEST_BIN% -C Debug --timeout 600
|
||||||
|
IF %errorlevel% neq 0 EXIT /B 1
|
||||||
|
|
||||||
|
CD ..
|
||||||
|
RMDIR /S /Q cmake_msvc2022
|
||||||
|
|
||||||
|
:: ----------------------------------------------------------------------------
|
||||||
|
:: Bazel
|
||||||
|
|
||||||
|
SET BAZEL_VS=C:\Program Files\Microsoft Visual Studio\2022\Community
|
||||||
|
%BAZEL_EXE% test ... ^
|
||||||
|
--compilation_mode=dbg ^
|
||||||
|
--copt=/std:c++14 ^
|
||||||
|
--copt=/WX ^
|
||||||
|
--features=external_include_paths ^
|
||||||
|
--keep_going ^
|
||||||
|
--test_output=errors ^
|
||||||
|
--test_tag_filters=-no_test_msvc2017
|
||||||
|
IF %errorlevel% neq 0 EXIT /B 1
|
|
@ -0,0 +1 @@
|
||||||
|
title: GoogleTest
|
|
@ -0,0 +1,43 @@
|
||||||
|
nav:
|
||||||
|
- section: "Get Started"
|
||||||
|
items:
|
||||||
|
- title: "Supported Platforms"
|
||||||
|
url: "/platforms.html"
|
||||||
|
- title: "Quickstart: Bazel"
|
||||||
|
url: "/quickstart-bazel.html"
|
||||||
|
- title: "Quickstart: CMake"
|
||||||
|
url: "/quickstart-cmake.html"
|
||||||
|
- section: "Guides"
|
||||||
|
items:
|
||||||
|
- title: "GoogleTest Primer"
|
||||||
|
url: "/primer.html"
|
||||||
|
- title: "Advanced Topics"
|
||||||
|
url: "/advanced.html"
|
||||||
|
- title: "Mocking for Dummies"
|
||||||
|
url: "/gmock_for_dummies.html"
|
||||||
|
- title: "Mocking Cookbook"
|
||||||
|
url: "/gmock_cook_book.html"
|
||||||
|
- title: "Mocking Cheat Sheet"
|
||||||
|
url: "/gmock_cheat_sheet.html"
|
||||||
|
- section: "References"
|
||||||
|
items:
|
||||||
|
- title: "Testing Reference"
|
||||||
|
url: "/reference/testing.html"
|
||||||
|
- title: "Mocking Reference"
|
||||||
|
url: "/reference/mocking.html"
|
||||||
|
- title: "Assertions"
|
||||||
|
url: "/reference/assertions.html"
|
||||||
|
- title: "Matchers"
|
||||||
|
url: "/reference/matchers.html"
|
||||||
|
- title: "Actions"
|
||||||
|
url: "/reference/actions.html"
|
||||||
|
- title: "Testing FAQ"
|
||||||
|
url: "/faq.html"
|
||||||
|
- title: "Mocking FAQ"
|
||||||
|
url: "/gmock_faq.html"
|
||||||
|
- title: "Code Samples"
|
||||||
|
url: "/samples.html"
|
||||||
|
- title: "Using pkg-config"
|
||||||
|
url: "/pkgconfig.html"
|
||||||
|
- title: "Community Documentation"
|
||||||
|
url: "/community_created_documentation.html"
|
|
@ -0,0 +1,58 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ site.lang | default: "en-US" }}">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
{% seo %}
|
||||||
|
<link rel="stylesheet" href="{{ "/assets/css/style.css?v=" | append: site.github.build_revision | relative_url }}">
|
||||||
|
<script>
|
||||||
|
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
|
||||||
|
ga('create', 'UA-197576187-1', { 'storage': 'none' });
|
||||||
|
ga('set', 'referrer', document.referrer.split('?')[0]);
|
||||||
|
ga('set', 'location', window.location.href.split('?')[0]);
|
||||||
|
ga('set', 'anonymizeIp', true);
|
||||||
|
ga('send', 'pageview');
|
||||||
|
</script>
|
||||||
|
<script async src='https://www.google-analytics.com/analytics.js'></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="sidebar">
|
||||||
|
<div class="header">
|
||||||
|
<h1><a href="{{ "/" | relative_url }}">{{ site.title | default: "Documentation" }}</a></h1>
|
||||||
|
</div>
|
||||||
|
<input type="checkbox" id="nav-toggle" class="nav-toggle">
|
||||||
|
<label for="nav-toggle" class="expander">
|
||||||
|
<span class="arrow"></span>
|
||||||
|
</label>
|
||||||
|
<nav>
|
||||||
|
{% for item in site.data.navigation.nav %}
|
||||||
|
<h2>{{ item.section }}</h2>
|
||||||
|
<ul>
|
||||||
|
{% for subitem in item.items %}
|
||||||
|
<a href="{{subitem.url | relative_url }}">
|
||||||
|
<li class="{% if subitem.url == page.url %}active{% endif %}">
|
||||||
|
{{ subitem.title }}
|
||||||
|
</li>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endfor %}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="main markdown-body">
|
||||||
|
<div class="main-inner">
|
||||||
|
{{ content }}
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
GoogleTest ·
|
||||||
|
<a href="https://github.com/google/googletest">GitHub Repository</a> ·
|
||||||
|
<a href="https://github.com/google/googletest/blob/main/LICENSE">License</a> ·
|
||||||
|
<a href="https://policies.google.com/privacy">Privacy Policy</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/anchor-js/4.1.0/anchor.min.js" integrity="sha256-lZaRhKri35AyJSypXXs4o6OPFTbTmUoltBbDCbdzegg=" crossorigin="anonymous"></script>
|
||||||
|
<script>anchors.add('.main h2, .main h3, .main h4, .main h5, .main h6');</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,200 @@
|
||||||
|
// Styles for GoogleTest docs website on GitHub Pages.
|
||||||
|
// Color variables are defined in
|
||||||
|
// https://github.com/pages-themes/primer/tree/master/_sass/primer-support/lib/variables
|
||||||
|
|
||||||
|
$sidebar-width: 260px;
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
background: $black;
|
||||||
|
color: $text-white;
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: auto;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
width: $sidebar-width;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar h1 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar h2 {
|
||||||
|
color: $gray-light;
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-weight: normal;
|
||||||
|
margin-bottom: 0.8em;
|
||||||
|
padding-left: 2.5em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .header {
|
||||||
|
background: $black;
|
||||||
|
padding: 2em;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .header a {
|
||||||
|
color: $text-white;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-toggle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .expander {
|
||||||
|
cursor: pointer;
|
||||||
|
display: none;
|
||||||
|
height: 3em;
|
||||||
|
position: absolute;
|
||||||
|
right: 1em;
|
||||||
|
top: 1.5em;
|
||||||
|
width: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .expander .arrow {
|
||||||
|
border: solid $white;
|
||||||
|
border-width: 0 3px 3px 0;
|
||||||
|
display: block;
|
||||||
|
height: 0.7em;
|
||||||
|
margin: 1em auto;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
transition: transform 0.5s;
|
||||||
|
width: 0.7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar nav {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar nav ul {
|
||||||
|
list-style-type: none;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
color: $text-white;
|
||||||
|
padding-left: 2em;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.active {
|
||||||
|
background: $border-gray-darker;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
li:hover {
|
||||||
|
background: $border-gray-darker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
background-color: $bg-gray;
|
||||||
|
width: calc(100% - #{$sidebar-width});
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .main-inner {
|
||||||
|
background-color: $white;
|
||||||
|
padding: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .footer {
|
||||||
|
margin: 0;
|
||||||
|
padding: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main table th {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .callout {
|
||||||
|
border-left: 0.25em solid $white;
|
||||||
|
padding: 1em;
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.important {
|
||||||
|
background-color: $bg-yellow-light;
|
||||||
|
border-color: $bg-yellow;
|
||||||
|
color: $black;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.note {
|
||||||
|
background-color: $bg-blue-light;
|
||||||
|
border-color: $text-blue;
|
||||||
|
color: $text-blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tip {
|
||||||
|
background-color: $green-000;
|
||||||
|
border-color: $green-700;
|
||||||
|
color: $green-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.warning {
|
||||||
|
background-color: $red-000;
|
||||||
|
border-color: $text-red;
|
||||||
|
color: $text-red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .good pre {
|
||||||
|
background-color: $bg-green-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .bad pre {
|
||||||
|
background-color: $red-000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (max-width: 768px) {
|
||||||
|
body {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
height: auto;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .expander {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar nav {
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .nav-toggle:checked {
|
||||||
|
& ~ nav {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
& + .expander .arrow {
|
||||||
|
transform: rotate(-135deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
---
|
||||||
|
|
||||||
|
@import "jekyll-theme-primer";
|
||||||
|
@import "main";
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Community-Created Documentation
|
||||||
|
|
||||||
|
The following is a list, in no particular order, of links to documentation
|
||||||
|
created by the Googletest community.
|
||||||
|
|
||||||
|
* [Googlemock Insights](https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles/blob/master/googletest/insights.md),
|
||||||
|
by [ElectricRCAircraftGuy](https://github.com/ElectricRCAircraftGuy)
|
|
@ -0,0 +1,692 @@
|
||||||
|
# GoogleTest FAQ
|
||||||
|
|
||||||
|
## Why should test suite names and test names not contain underscore?
|
||||||
|
|
||||||
|
{: .callout .note}
|
||||||
|
Note: GoogleTest reserves underscore (`_`) for special purpose keywords, such as
|
||||||
|
[the `DISABLED_` prefix](advanced.md#temporarily-disabling-tests), in addition
|
||||||
|
to the following rationale.
|
||||||
|
|
||||||
|
Underscore (`_`) is special, as C++ reserves the following to be used by the
|
||||||
|
compiler and the standard library:
|
||||||
|
|
||||||
|
1. any identifier that starts with an `_` followed by an upper-case letter, and
|
||||||
|
2. any identifier that contains two consecutive underscores (i.e. `__`)
|
||||||
|
*anywhere* in its name.
|
||||||
|
|
||||||
|
User code is *prohibited* from using such identifiers.
|
||||||
|
|
||||||
|
Now let's look at what this means for `TEST` and `TEST_F`.
|
||||||
|
|
||||||
|
Currently `TEST(TestSuiteName, TestName)` generates a class named
|
||||||
|
`TestSuiteName_TestName_Test`. What happens if `TestSuiteName` or `TestName`
|
||||||
|
contains `_`?
|
||||||
|
|
||||||
|
1. If `TestSuiteName` starts with an `_` followed by an upper-case letter (say,
|
||||||
|
`_Foo`), we end up with `_Foo_TestName_Test`, which is reserved and thus
|
||||||
|
invalid.
|
||||||
|
2. If `TestSuiteName` ends with an `_` (say, `Foo_`), we get
|
||||||
|
`Foo__TestName_Test`, which is invalid.
|
||||||
|
3. If `TestName` starts with an `_` (say, `_Bar`), we get
|
||||||
|
`TestSuiteName__Bar_Test`, which is invalid.
|
||||||
|
4. If `TestName` ends with an `_` (say, `Bar_`), we get
|
||||||
|
`TestSuiteName_Bar__Test`, which is invalid.
|
||||||
|
|
||||||
|
So clearly `TestSuiteName` and `TestName` cannot start or end with `_`
|
||||||
|
(Actually, `TestSuiteName` can start with `_` -- as long as the `_` isn't
|
||||||
|
followed by an upper-case letter. But that's getting complicated. So for
|
||||||
|
simplicity we just say that it cannot start with `_`.).
|
||||||
|
|
||||||
|
It may seem fine for `TestSuiteName` and `TestName` to contain `_` in the
|
||||||
|
middle. However, consider this:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
TEST(Time, Flies_Like_An_Arrow) { ... }
|
||||||
|
TEST(Time_Flies, Like_An_Arrow) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, the two `TEST`s will both generate the same class
|
||||||
|
(`Time_Flies_Like_An_Arrow_Test`). That's not good.
|
||||||
|
|
||||||
|
So for simplicity, we just ask the users to avoid `_` in `TestSuiteName` and
|
||||||
|
`TestName`. The rule is more constraining than necessary, but it's simple and
|
||||||
|
easy to remember. It also gives GoogleTest some wiggle room in case its
|
||||||
|
implementation needs to change in the future.
|
||||||
|
|
||||||
|
If you violate the rule, there may not be immediate consequences, but your test
|
||||||
|
may (just may) break with a new compiler (or a new version of the compiler you
|
||||||
|
are using) or with a new version of GoogleTest. Therefore it's best to follow
|
||||||
|
the rule.
|
||||||
|
|
||||||
|
## Why does GoogleTest support `EXPECT_EQ(NULL, ptr)` and `ASSERT_EQ(NULL, ptr)` but not `EXPECT_NE(NULL, ptr)` and `ASSERT_NE(NULL, ptr)`?
|
||||||
|
|
||||||
|
First of all, you can use `nullptr` with each of these macros, e.g.
|
||||||
|
`EXPECT_EQ(ptr, nullptr)`, `EXPECT_NE(ptr, nullptr)`, `ASSERT_EQ(ptr, nullptr)`,
|
||||||
|
`ASSERT_NE(ptr, nullptr)`. This is the preferred syntax in the style guide
|
||||||
|
because `nullptr` does not have the type problems that `NULL` does.
|
||||||
|
|
||||||
|
Due to some peculiarity of C++, it requires some non-trivial template meta
|
||||||
|
programming tricks to support using `NULL` as an argument of the `EXPECT_XX()`
|
||||||
|
and `ASSERT_XX()` macros. Therefore we only do it where it's most needed
|
||||||
|
(otherwise we make the implementation of GoogleTest harder to maintain and more
|
||||||
|
error-prone than necessary).
|
||||||
|
|
||||||
|
Historically, the `EXPECT_EQ()` macro took the *expected* value as its first
|
||||||
|
argument and the *actual* value as the second, though this argument order is now
|
||||||
|
discouraged. It was reasonable that someone wanted
|
||||||
|
to write `EXPECT_EQ(NULL, some_expression)`, and this indeed was requested
|
||||||
|
several times. Therefore we implemented it.
|
||||||
|
|
||||||
|
The need for `EXPECT_NE(NULL, ptr)` wasn't nearly as strong. When the assertion
|
||||||
|
fails, you already know that `ptr` must be `NULL`, so it doesn't add any
|
||||||
|
information to print `ptr` in this case. That means `EXPECT_TRUE(ptr != NULL)`
|
||||||
|
works just as well.
|
||||||
|
|
||||||
|
If we were to support `EXPECT_NE(NULL, ptr)`, for consistency we'd have to
|
||||||
|
support `EXPECT_NE(ptr, NULL)` as well. This means using the template meta
|
||||||
|
programming tricks twice in the implementation, making it even harder to
|
||||||
|
understand and maintain. We believe the benefit doesn't justify the cost.
|
||||||
|
|
||||||
|
Finally, with the growth of the gMock matcher library, we are encouraging people
|
||||||
|
to use the unified `EXPECT_THAT(value, matcher)` syntax more often in tests. One
|
||||||
|
significant advantage of the matcher approach is that matchers can be easily
|
||||||
|
combined to form new matchers, while the `EXPECT_NE`, etc, macros cannot be
|
||||||
|
easily combined. Therefore we want to invest more in the matchers than in the
|
||||||
|
`EXPECT_XX()` macros.
|
||||||
|
|
||||||
|
## I need to test that different implementations of an interface satisfy some common requirements. Should I use typed tests or value-parameterized tests?
|
||||||
|
|
||||||
|
For testing various implementations of the same interface, either typed tests or
|
||||||
|
value-parameterized tests can get it done. It's really up to you the user to
|
||||||
|
decide which is more convenient for you, depending on your particular case. Some
|
||||||
|
rough guidelines:
|
||||||
|
|
||||||
|
* Typed tests can be easier to write if instances of the different
|
||||||
|
implementations can be created the same way, modulo the type. For example,
|
||||||
|
if all these implementations have a public default constructor (such that
|
||||||
|
you can write `new TypeParam`), or if their factory functions have the same
|
||||||
|
form (e.g. `CreateInstance<TypeParam>()`).
|
||||||
|
* Value-parameterized tests can be easier to write if you need different code
|
||||||
|
patterns to create different implementations' instances, e.g. `new Foo` vs
|
||||||
|
`new Bar(5)`. To accommodate for the differences, you can write factory
|
||||||
|
function wrappers and pass these function pointers to the tests as their
|
||||||
|
parameters.
|
||||||
|
* When a typed test fails, the default output includes the name of the type,
|
||||||
|
which can help you quickly identify which implementation is wrong.
|
||||||
|
Value-parameterized tests only show the number of the failed iteration by
|
||||||
|
default. You will need to define a function that returns the iteration name
|
||||||
|
and pass it as the third parameter to INSTANTIATE_TEST_SUITE_P to have more
|
||||||
|
useful output.
|
||||||
|
* When using typed tests, you need to make sure you are testing against the
|
||||||
|
interface type, not the concrete types (in other words, you want to make
|
||||||
|
sure `implicit_cast<MyInterface*>(my_concrete_impl)` works, not just that
|
||||||
|
`my_concrete_impl` works). It's less likely to make mistakes in this area
|
||||||
|
when using value-parameterized tests.
|
||||||
|
|
||||||
|
I hope I didn't confuse you more. :-) If you don't mind, I'd suggest you to give
|
||||||
|
both approaches a try. Practice is a much better way to grasp the subtle
|
||||||
|
differences between the two tools. Once you have some concrete experience, you
|
||||||
|
can much more easily decide which one to use the next time.
|
||||||
|
|
||||||
|
## I got some run-time errors about invalid proto descriptors when using `ProtocolMessageEquals`. Help!
|
||||||
|
|
||||||
|
{: .callout .note}
|
||||||
|
**Note:** `ProtocolMessageEquals` and `ProtocolMessageEquiv` are *deprecated*
|
||||||
|
now. Please use `EqualsProto`, etc instead.
|
||||||
|
|
||||||
|
`ProtocolMessageEquals` and `ProtocolMessageEquiv` were redefined recently and
|
||||||
|
are now less tolerant of invalid protocol buffer definitions. In particular, if
|
||||||
|
you have a `foo.proto` that doesn't fully qualify the type of a protocol message
|
||||||
|
it references (e.g. `message<Bar>` where it should be `message<blah.Bar>`), you
|
||||||
|
will now get run-time errors like:
|
||||||
|
|
||||||
|
```
|
||||||
|
... descriptor.cc:...] Invalid proto descriptor for file "path/to/foo.proto":
|
||||||
|
... descriptor.cc:...] blah.MyMessage.my_field: ".Bar" is not defined.
|
||||||
|
```
|
||||||
|
|
||||||
|
If you see this, your `.proto` file is broken and needs to be fixed by making
|
||||||
|
the types fully qualified. The new definition of `ProtocolMessageEquals` and
|
||||||
|
`ProtocolMessageEquiv` just happen to reveal your bug.
|
||||||
|
|
||||||
|
## My death test modifies some state, but the change seems lost after the death test finishes. Why?
|
||||||
|
|
||||||
|
Death tests (`EXPECT_DEATH`, etc) are executed in a sub-process s.t. the
|
||||||
|
expected crash won't kill the test program (i.e. the parent process). As a
|
||||||
|
result, any in-memory side effects they incur are observable in their respective
|
||||||
|
sub-processes, but not in the parent process. You can think of them as running
|
||||||
|
in a parallel universe, more or less.
|
||||||
|
|
||||||
|
In particular, if you use mocking and the death test statement invokes some mock
|
||||||
|
methods, the parent process will think the calls have never occurred. Therefore,
|
||||||
|
you may want to move your `EXPECT_CALL` statements inside the `EXPECT_DEATH`
|
||||||
|
macro.
|
||||||
|
|
||||||
|
## EXPECT_EQ(htonl(blah), blah_blah) generates weird compiler errors in opt mode. Is this a GoogleTest bug?
|
||||||
|
|
||||||
|
Actually, the bug is in `htonl()`.
|
||||||
|
|
||||||
|
According to `'man htonl'`, `htonl()` is a *function*, which means it's valid to
|
||||||
|
use `htonl` as a function pointer. However, in opt mode `htonl()` is defined as
|
||||||
|
a *macro*, which breaks this usage.
|
||||||
|
|
||||||
|
Worse, the macro definition of `htonl()` uses a `gcc` extension and is *not*
|
||||||
|
standard C++. That hacky implementation has some ad hoc limitations. In
|
||||||
|
particular, it prevents you from writing `Foo<sizeof(htonl(x))>()`, where `Foo`
|
||||||
|
is a template that has an integral argument.
|
||||||
|
|
||||||
|
The implementation of `EXPECT_EQ(a, b)` uses `sizeof(... a ...)` inside a
|
||||||
|
template argument, and thus doesn't compile in opt mode when `a` contains a call
|
||||||
|
to `htonl()`. It is difficult to make `EXPECT_EQ` bypass the `htonl()` bug, as
|
||||||
|
the solution must work with different compilers on various platforms.
|
||||||
|
|
||||||
|
## The compiler complains about "undefined references" to some static const member variables, but I did define them in the class body. What's wrong?
|
||||||
|
|
||||||
|
If your class has a static data member:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// foo.h
|
||||||
|
class Foo {
|
||||||
|
...
|
||||||
|
static const int kBar = 100;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
You also need to define it *outside* of the class body in `foo.cc`:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
const int Foo::kBar; // No initializer here.
|
||||||
|
```
|
||||||
|
|
||||||
|
Otherwise your code is **invalid C++**, and may break in unexpected ways. In
|
||||||
|
particular, using it in GoogleTest comparison assertions (`EXPECT_EQ`, etc) will
|
||||||
|
generate an "undefined reference" linker error. The fact that "it used to work"
|
||||||
|
doesn't mean it's valid. It just means that you were lucky. :-)
|
||||||
|
|
||||||
|
If the declaration of the static data member is `constexpr` then it is
|
||||||
|
implicitly an `inline` definition, and a separate definition in `foo.cc` is not
|
||||||
|
needed:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// foo.h
|
||||||
|
class Foo {
|
||||||
|
...
|
||||||
|
static constexpr int kBar = 100; // Defines kBar, no need to do it in foo.cc.
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Can I derive a test fixture from another?
|
||||||
|
|
||||||
|
Yes.
|
||||||
|
|
||||||
|
Each test fixture has a corresponding and same named test suite. This means only
|
||||||
|
one test suite can use a particular fixture. Sometimes, however, multiple test
|
||||||
|
cases may want to use the same or slightly different fixtures. For example, you
|
||||||
|
may want to make sure that all of a GUI library's test suites don't leak
|
||||||
|
important system resources like fonts and brushes.
|
||||||
|
|
||||||
|
In GoogleTest, you share a fixture among test suites by putting the shared logic
|
||||||
|
in a base test fixture, then deriving from that base a separate fixture for each
|
||||||
|
test suite that wants to use this common logic. You then use `TEST_F()` to write
|
||||||
|
tests using each derived fixture.
|
||||||
|
|
||||||
|
Typically, your code looks like this:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// Defines a base test fixture.
|
||||||
|
class BaseTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
|
// Derives a fixture FooTest from BaseTest.
|
||||||
|
class FooTest : public BaseTest {
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
BaseTest::SetUp(); // Sets up the base fixture first.
|
||||||
|
... additional set-up work ...
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
... clean-up work for FooTest ...
|
||||||
|
BaseTest::TearDown(); // Remember to tear down the base fixture
|
||||||
|
// after cleaning up FooTest!
|
||||||
|
}
|
||||||
|
|
||||||
|
... functions and variables for FooTest ...
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tests that use the fixture FooTest.
|
||||||
|
TEST_F(FooTest, Bar) { ... }
|
||||||
|
TEST_F(FooTest, Baz) { ... }
|
||||||
|
|
||||||
|
... additional fixtures derived from BaseTest ...
|
||||||
|
```
|
||||||
|
|
||||||
|
If necessary, you can continue to derive test fixtures from a derived fixture.
|
||||||
|
GoogleTest has no limit on how deep the hierarchy can be.
|
||||||
|
|
||||||
|
For a complete example using derived test fixtures, see
|
||||||
|
[sample5_unittest.cc](https://github.com/google/googletest/blob/main/googletest/samples/sample5_unittest.cc).
|
||||||
|
|
||||||
|
## My compiler complains "void value not ignored as it ought to be." What does this mean?
|
||||||
|
|
||||||
|
You're probably using an `ASSERT_*()` in a function that doesn't return `void`.
|
||||||
|
`ASSERT_*()` can only be used in `void` functions, due to exceptions being
|
||||||
|
disabled by our build system. Please see more details
|
||||||
|
[here](advanced.md#assertion-placement).
|
||||||
|
|
||||||
|
## My death test hangs (or seg-faults). How do I fix it?
|
||||||
|
|
||||||
|
In GoogleTest, death tests are run in a child process and the way they work is
|
||||||
|
delicate. To write death tests you really need to understand how they work—see
|
||||||
|
the details at [Death Assertions](reference/assertions.md#death) in the
|
||||||
|
Assertions Reference.
|
||||||
|
|
||||||
|
In particular, death tests don't like having multiple threads in the parent
|
||||||
|
process. So the first thing you can try is to eliminate creating threads outside
|
||||||
|
of `EXPECT_DEATH()`. For example, you may want to use mocks or fake objects
|
||||||
|
instead of real ones in your tests.
|
||||||
|
|
||||||
|
Sometimes this is impossible as some library you must use may be creating
|
||||||
|
threads before `main()` is even reached. In this case, you can try to minimize
|
||||||
|
the chance of conflicts by either moving as many activities as possible inside
|
||||||
|
`EXPECT_DEATH()` (in the extreme case, you want to move everything inside), or
|
||||||
|
leaving as few things as possible in it. Also, you can try to set the death test
|
||||||
|
style to `"threadsafe"`, which is safer but slower, and see if it helps.
|
||||||
|
|
||||||
|
If you go with thread-safe death tests, remember that they rerun the test
|
||||||
|
program from the beginning in the child process. Therefore make sure your
|
||||||
|
program can run side-by-side with itself and is deterministic.
|
||||||
|
|
||||||
|
In the end, this boils down to good concurrent programming. You have to make
|
||||||
|
sure that there are no race conditions or deadlocks in your program. No silver
|
||||||
|
bullet - sorry!
|
||||||
|
|
||||||
|
## Should I use the constructor/destructor of the test fixture or SetUp()/TearDown()? {#CtorVsSetUp}
|
||||||
|
|
||||||
|
The first thing to remember is that GoogleTest does **not** reuse the same test
|
||||||
|
fixture object across multiple tests. For each `TEST_F`, GoogleTest will create
|
||||||
|
a **fresh** test fixture object, immediately call `SetUp()`, run the test body,
|
||||||
|
call `TearDown()`, and then delete the test fixture object.
|
||||||
|
|
||||||
|
When you need to write per-test set-up and tear-down logic, you have the choice
|
||||||
|
between using the test fixture constructor/destructor or `SetUp()/TearDown()`.
|
||||||
|
The former is usually preferred, as it has the following benefits:
|
||||||
|
|
||||||
|
* By initializing a member variable in the constructor, we have the option to
|
||||||
|
make it `const`, which helps prevent accidental changes to its value and
|
||||||
|
makes the tests more obviously correct.
|
||||||
|
* In case we need to subclass the test fixture class, the subclass'
|
||||||
|
constructor is guaranteed to call the base class' constructor *first*, and
|
||||||
|
the subclass' destructor is guaranteed to call the base class' destructor
|
||||||
|
*afterward*. With `SetUp()/TearDown()`, a subclass may make the mistake of
|
||||||
|
forgetting to call the base class' `SetUp()/TearDown()` or call them at the
|
||||||
|
wrong time.
|
||||||
|
|
||||||
|
You may still want to use `SetUp()/TearDown()` in the following cases:
|
||||||
|
|
||||||
|
* C++ does not allow virtual function calls in constructors and destructors.
|
||||||
|
You can call a method declared as virtual, but it will not use dynamic
|
||||||
|
dispatch. It will use the definition from the class the constructor of which
|
||||||
|
is currently executing. This is because calling a virtual method before the
|
||||||
|
derived class constructor has a chance to run is very dangerous - the
|
||||||
|
virtual method might operate on uninitialized data. Therefore, if you need
|
||||||
|
to call a method that will be overridden in a derived class, you have to use
|
||||||
|
`SetUp()/TearDown()`.
|
||||||
|
* In the body of a constructor (or destructor), it's not possible to use the
|
||||||
|
`ASSERT_xx` macros. Therefore, if the set-up operation could cause a fatal
|
||||||
|
test failure that should prevent the test from running, it's necessary to
|
||||||
|
use `abort` and abort the whole test
|
||||||
|
executable, or to use `SetUp()` instead of a constructor.
|
||||||
|
* If the tear-down operation could throw an exception, you must use
|
||||||
|
`TearDown()` as opposed to the destructor, as throwing in a destructor leads
|
||||||
|
to undefined behavior and usually will kill your program right away. Note
|
||||||
|
that many standard libraries (like STL) may throw when exceptions are
|
||||||
|
enabled in the compiler. Therefore you should prefer `TearDown()` if you
|
||||||
|
want to write portable tests that work with or without exceptions.
|
||||||
|
* The GoogleTest team is considering making the assertion macros throw on
|
||||||
|
platforms where exceptions are enabled (e.g. Windows, Mac OS, and Linux
|
||||||
|
client-side), which will eliminate the need for the user to propagate
|
||||||
|
failures from a subroutine to its caller. Therefore, you shouldn't use
|
||||||
|
GoogleTest assertions in a destructor if your code could run on such a
|
||||||
|
platform.
|
||||||
|
|
||||||
|
## The compiler complains "no matching function to call" when I use ASSERT_PRED*. How do I fix it?
|
||||||
|
|
||||||
|
See details for [`EXPECT_PRED*`](reference/assertions.md#EXPECT_PRED) in the
|
||||||
|
Assertions Reference.
|
||||||
|
|
||||||
|
## My compiler complains about "ignoring return value" when I call RUN_ALL_TESTS(). Why?
|
||||||
|
|
||||||
|
Some people had been ignoring the return value of `RUN_ALL_TESTS()`. That is,
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```c++
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
```
|
||||||
|
|
||||||
|
they write
|
||||||
|
|
||||||
|
```c++
|
||||||
|
RUN_ALL_TESTS();
|
||||||
|
```
|
||||||
|
|
||||||
|
This is **wrong and dangerous**. The testing services needs to see the return
|
||||||
|
value of `RUN_ALL_TESTS()` in order to determine if a test has passed. If your
|
||||||
|
`main()` function ignores it, your test will be considered successful even if it
|
||||||
|
has a GoogleTest assertion failure. Very bad.
|
||||||
|
|
||||||
|
We have decided to fix this (thanks to Michael Chastain for the idea). Now, your
|
||||||
|
code will no longer be able to ignore `RUN_ALL_TESTS()` when compiled with
|
||||||
|
`gcc`. If you do so, you'll get a compiler error.
|
||||||
|
|
||||||
|
If you see the compiler complaining about you ignoring the return value of
|
||||||
|
`RUN_ALL_TESTS()`, the fix is simple: just make sure its value is used as the
|
||||||
|
return value of `main()`.
|
||||||
|
|
||||||
|
But how could we introduce a change that breaks existing tests? Well, in this
|
||||||
|
case, the code was already broken in the first place, so we didn't break it. :-)
|
||||||
|
|
||||||
|
## My compiler complains that a constructor (or destructor) cannot return a value. What's going on?
|
||||||
|
|
||||||
|
Due to a peculiarity of C++, in order to support the syntax for streaming
|
||||||
|
messages to an `ASSERT_*`, e.g.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
ASSERT_EQ(1, Foo()) << "blah blah" << foo;
|
||||||
|
```
|
||||||
|
|
||||||
|
we had to give up using `ASSERT*` and `FAIL*` (but not `EXPECT*` and
|
||||||
|
`ADD_FAILURE*`) in constructors and destructors. The workaround is to move the
|
||||||
|
content of your constructor/destructor to a private void member function, or
|
||||||
|
switch to `EXPECT_*()` if that works. This
|
||||||
|
[section](advanced.md#assertion-placement) in the user's guide explains it.
|
||||||
|
|
||||||
|
## My SetUp() function is not called. Why?
|
||||||
|
|
||||||
|
C++ is case-sensitive. Did you spell it as `Setup()`?
|
||||||
|
|
||||||
|
Similarly, sometimes people spell `SetUpTestSuite()` as `SetupTestSuite()` and
|
||||||
|
wonder why it's never called.
|
||||||
|
|
||||||
|
## I have several test suites which share the same test fixture logic, do I have to define a new test fixture class for each of them? This seems pretty tedious.
|
||||||
|
|
||||||
|
You don't have to. Instead of
|
||||||
|
|
||||||
|
```c++
|
||||||
|
class FooTest : public BaseTest {};
|
||||||
|
|
||||||
|
TEST_F(FooTest, Abc) { ... }
|
||||||
|
TEST_F(FooTest, Def) { ... }
|
||||||
|
|
||||||
|
class BarTest : public BaseTest {};
|
||||||
|
|
||||||
|
TEST_F(BarTest, Abc) { ... }
|
||||||
|
TEST_F(BarTest, Def) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
you can simply `typedef` the test fixtures:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
typedef BaseTest FooTest;
|
||||||
|
|
||||||
|
TEST_F(FooTest, Abc) { ... }
|
||||||
|
TEST_F(FooTest, Def) { ... }
|
||||||
|
|
||||||
|
typedef BaseTest BarTest;
|
||||||
|
|
||||||
|
TEST_F(BarTest, Abc) { ... }
|
||||||
|
TEST_F(BarTest, Def) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
## GoogleTest output is buried in a whole bunch of LOG messages. What do I do?
|
||||||
|
|
||||||
|
The GoogleTest output is meant to be a concise and human-friendly report. If
|
||||||
|
your test generates textual output itself, it will mix with the GoogleTest
|
||||||
|
output, making it hard to read. However, there is an easy solution to this
|
||||||
|
problem.
|
||||||
|
|
||||||
|
Since `LOG` messages go to stderr, we decided to let GoogleTest output go to
|
||||||
|
stdout. This way, you can easily separate the two using redirection. For
|
||||||
|
example:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ ./my_test > gtest_output.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Why should I prefer test fixtures over global variables?
|
||||||
|
|
||||||
|
There are several good reasons:
|
||||||
|
|
||||||
|
1. It's likely your test needs to change the states of its global variables.
|
||||||
|
This makes it difficult to keep side effects from escaping one test and
|
||||||
|
contaminating others, making debugging difficult. By using fixtures, each
|
||||||
|
test has a fresh set of variables that's different (but with the same
|
||||||
|
names). Thus, tests are kept independent of each other.
|
||||||
|
2. Global variables pollute the global namespace.
|
||||||
|
3. Test fixtures can be reused via subclassing, which cannot be done easily
|
||||||
|
with global variables. This is useful if many test suites have something in
|
||||||
|
common.
|
||||||
|
|
||||||
|
## What can the statement argument in ASSERT_DEATH() be?
|
||||||
|
|
||||||
|
`ASSERT_DEATH(statement, matcher)` (or any death assertion macro) can be used
|
||||||
|
wherever *`statement`* is valid. So basically *`statement`* can be any C++
|
||||||
|
statement that makes sense in the current context. In particular, it can
|
||||||
|
reference global and/or local variables, and can be:
|
||||||
|
|
||||||
|
* a simple function call (often the case),
|
||||||
|
* a complex expression, or
|
||||||
|
* a compound statement.
|
||||||
|
|
||||||
|
Some examples are shown here:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// A death test can be a simple function call.
|
||||||
|
TEST(MyDeathTest, FunctionCall) {
|
||||||
|
ASSERT_DEATH(Xyz(5), "Xyz failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Or a complex expression that references variables and functions.
|
||||||
|
TEST(MyDeathTest, ComplexExpression) {
|
||||||
|
const bool c = Condition();
|
||||||
|
ASSERT_DEATH((c ? Func1(0) : object2.Method("test")),
|
||||||
|
"(Func1|Method) failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Death assertions can be used anywhere in a function. In
|
||||||
|
// particular, they can be inside a loop.
|
||||||
|
TEST(MyDeathTest, InsideLoop) {
|
||||||
|
// Verifies that Foo(0), Foo(1), ..., and Foo(4) all die.
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
EXPECT_DEATH_M(Foo(i), "Foo has \\d+ errors",
|
||||||
|
::testing::Message() << "where i is " << i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A death assertion can contain a compound statement.
|
||||||
|
TEST(MyDeathTest, CompoundStatement) {
|
||||||
|
// Verifies that at lease one of Bar(0), Bar(1), ..., and
|
||||||
|
// Bar(4) dies.
|
||||||
|
ASSERT_DEATH({
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
Bar(i);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Bar has \\d+ errors");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## I have a fixture class `FooTest`, but `TEST_F(FooTest, Bar)` gives me error ``"no matching function for call to `FooTest::FooTest()'"``. Why?
|
||||||
|
|
||||||
|
GoogleTest needs to be able to create objects of your test fixture class, so it
|
||||||
|
must have a default constructor. Normally the compiler will define one for you.
|
||||||
|
However, there are cases where you have to define your own:
|
||||||
|
|
||||||
|
* If you explicitly declare a non-default constructor for class `FooTest`
|
||||||
|
(`DISALLOW_EVIL_CONSTRUCTORS()` does this), then you need to define a
|
||||||
|
default constructor, even if it would be empty.
|
||||||
|
* If `FooTest` has a const non-static data member, then you have to define the
|
||||||
|
default constructor *and* initialize the const member in the initializer
|
||||||
|
list of the constructor. (Early versions of `gcc` doesn't force you to
|
||||||
|
initialize the const member. It's a bug that has been fixed in `gcc 4`.)
|
||||||
|
|
||||||
|
## Why does ASSERT_DEATH complain about previous threads that were already joined?
|
||||||
|
|
||||||
|
With the Linux pthread library, there is no turning back once you cross the line
|
||||||
|
from a single thread to multiple threads. The first time you create a thread, a
|
||||||
|
manager thread is created in addition, so you get 3, not 2, threads. Later when
|
||||||
|
the thread you create joins the main thread, the thread count decrements by 1,
|
||||||
|
but the manager thread will never be killed, so you still have 2 threads, which
|
||||||
|
means you cannot safely run a death test.
|
||||||
|
|
||||||
|
The new NPTL thread library doesn't suffer from this problem, as it doesn't
|
||||||
|
create a manager thread. However, if you don't control which machine your test
|
||||||
|
runs on, you shouldn't depend on this.
|
||||||
|
|
||||||
|
## Why does GoogleTest require the entire test suite, instead of individual tests, to be named *DeathTest when it uses ASSERT_DEATH?
|
||||||
|
|
||||||
|
GoogleTest does not interleave tests from different test suites. That is, it
|
||||||
|
runs all tests in one test suite first, and then runs all tests in the next test
|
||||||
|
suite, and so on. GoogleTest does this because it needs to set up a test suite
|
||||||
|
before the first test in it is run, and tear it down afterwards. Splitting up
|
||||||
|
the test case would require multiple set-up and tear-down processes, which is
|
||||||
|
inefficient and makes the semantics unclean.
|
||||||
|
|
||||||
|
If we were to determine the order of tests based on test name instead of test
|
||||||
|
case name, then we would have a problem with the following situation:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
TEST_F(FooTest, AbcDeathTest) { ... }
|
||||||
|
TEST_F(FooTest, Uvw) { ... }
|
||||||
|
|
||||||
|
TEST_F(BarTest, DefDeathTest) { ... }
|
||||||
|
TEST_F(BarTest, Xyz) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
Since `FooTest.AbcDeathTest` needs to run before `BarTest.Xyz`, and we don't
|
||||||
|
interleave tests from different test suites, we need to run all tests in the
|
||||||
|
`FooTest` case before running any test in the `BarTest` case. This contradicts
|
||||||
|
with the requirement to run `BarTest.DefDeathTest` before `FooTest.Uvw`.
|
||||||
|
|
||||||
|
## But I don't like calling my entire test suite \*DeathTest when it contains both death tests and non-death tests. What do I do?
|
||||||
|
|
||||||
|
You don't have to, but if you like, you may split up the test suite into
|
||||||
|
`FooTest` and `FooDeathTest`, where the names make it clear that they are
|
||||||
|
related:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
class FooTest : public ::testing::Test { ... };
|
||||||
|
|
||||||
|
TEST_F(FooTest, Abc) { ... }
|
||||||
|
TEST_F(FooTest, Def) { ... }
|
||||||
|
|
||||||
|
using FooDeathTest = FooTest;
|
||||||
|
|
||||||
|
TEST_F(FooDeathTest, Uvw) { ... EXPECT_DEATH(...) ... }
|
||||||
|
TEST_F(FooDeathTest, Xyz) { ... ASSERT_DEATH(...) ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
## GoogleTest prints the LOG messages in a death test's child process only when the test fails. How can I see the LOG messages when the death test succeeds?
|
||||||
|
|
||||||
|
Printing the LOG messages generated by the statement inside `EXPECT_DEATH()`
|
||||||
|
makes it harder to search for real problems in the parent's log. Therefore,
|
||||||
|
GoogleTest only prints them when the death test has failed.
|
||||||
|
|
||||||
|
If you really need to see such LOG messages, a workaround is to temporarily
|
||||||
|
break the death test (e.g. by changing the regex pattern it is expected to
|
||||||
|
match). Admittedly, this is a hack. We'll consider a more permanent solution
|
||||||
|
after the fork-and-exec-style death tests are implemented.
|
||||||
|
|
||||||
|
## The compiler complains about `no match for 'operator<<'` when I use an assertion. What gives?
|
||||||
|
|
||||||
|
If you use a user-defined type `FooType` in an assertion, you must make sure
|
||||||
|
there is an `std::ostream& operator<<(std::ostream&, const FooType&)` function
|
||||||
|
defined such that we can print a value of `FooType`.
|
||||||
|
|
||||||
|
In addition, if `FooType` is declared in a name space, the `<<` operator also
|
||||||
|
needs to be defined in the *same* name space. See
|
||||||
|
[Tip of the Week #49](http://abseil.io/tips/49) for details.
|
||||||
|
|
||||||
|
## How do I suppress the memory leak messages on Windows?
|
||||||
|
|
||||||
|
Since the statically initialized GoogleTest singleton requires allocations on
|
||||||
|
the heap, the Visual C++ memory leak detector will report memory leaks at the
|
||||||
|
end of the program run. The easiest way to avoid this is to use the
|
||||||
|
`_CrtMemCheckpoint` and `_CrtMemDumpAllObjectsSince` calls to not report any
|
||||||
|
statically initialized heap objects. See MSDN for more details and additional
|
||||||
|
heap check/debug routines.
|
||||||
|
|
||||||
|
## How can my code detect if it is running in a test?
|
||||||
|
|
||||||
|
If you write code that sniffs whether it's running in a test and does different
|
||||||
|
things accordingly, you are leaking test-only logic into production code and
|
||||||
|
there is no easy way to ensure that the test-only code paths aren't run by
|
||||||
|
mistake in production. Such cleverness also leads to
|
||||||
|
[Heisenbugs](https://en.wikipedia.org/wiki/Heisenbug). Therefore we strongly
|
||||||
|
advise against the practice, and GoogleTest doesn't provide a way to do it.
|
||||||
|
|
||||||
|
In general, the recommended way to cause the code to behave differently under
|
||||||
|
test is [Dependency Injection](http://en.wikipedia.org/wiki/Dependency_injection). You can inject
|
||||||
|
different functionality from the test and from the production code. Since your
|
||||||
|
production code doesn't link in the for-test logic at all (the
|
||||||
|
[`testonly`](http://docs.bazel.build/versions/master/be/common-definitions.html#common.testonly) attribute for BUILD targets helps to ensure
|
||||||
|
that), there is no danger in accidentally running it.
|
||||||
|
|
||||||
|
However, if you *really*, *really*, *really* have no choice, and if you follow
|
||||||
|
the rule of ending your test program names with `_test`, you can use the
|
||||||
|
*horrible* hack of sniffing your executable name (`argv[0]` in `main()`) to know
|
||||||
|
whether the code is under test.
|
||||||
|
|
||||||
|
## How do I temporarily disable a test?
|
||||||
|
|
||||||
|
If you have a broken test that you cannot fix right away, you can add the
|
||||||
|
`DISABLED_` prefix to its name. This will exclude it from execution. This is
|
||||||
|
better than commenting out the code or using `#if 0`, as disabled tests are
|
||||||
|
still compiled (and thus won't rot).
|
||||||
|
|
||||||
|
To include disabled tests in test execution, just invoke the test program with
|
||||||
|
the `--gtest_also_run_disabled_tests` flag.
|
||||||
|
|
||||||
|
## Is it OK if I have two separate `TEST(Foo, Bar)` test methods defined in different namespaces?
|
||||||
|
|
||||||
|
Yes.
|
||||||
|
|
||||||
|
The rule is **all test methods in the same test suite must use the same fixture
|
||||||
|
class.** This means that the following is **allowed** because both tests use the
|
||||||
|
same fixture class (`::testing::Test`).
|
||||||
|
|
||||||
|
```c++
|
||||||
|
namespace foo {
|
||||||
|
TEST(CoolTest, DoSomething) {
|
||||||
|
SUCCEED();
|
||||||
|
}
|
||||||
|
} // namespace foo
|
||||||
|
|
||||||
|
namespace bar {
|
||||||
|
TEST(CoolTest, DoSomething) {
|
||||||
|
SUCCEED();
|
||||||
|
}
|
||||||
|
} // namespace bar
|
||||||
|
```
|
||||||
|
|
||||||
|
However, the following code is **not allowed** and will produce a runtime error
|
||||||
|
from GoogleTest because the test methods are using different test fixture
|
||||||
|
classes with the same test suite name.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
namespace foo {
|
||||||
|
class CoolTest : public ::testing::Test {}; // Fixture foo::CoolTest
|
||||||
|
TEST_F(CoolTest, DoSomething) {
|
||||||
|
SUCCEED();
|
||||||
|
}
|
||||||
|
} // namespace foo
|
||||||
|
|
||||||
|
namespace bar {
|
||||||
|
class CoolTest : public ::testing::Test {}; // Fixture: bar::CoolTest
|
||||||
|
TEST_F(CoolTest, DoSomething) {
|
||||||
|
SUCCEED();
|
||||||
|
}
|
||||||
|
} // namespace bar
|
||||||
|
```
|
|
@ -0,0 +1,241 @@
|
||||||
|
# gMock Cheat Sheet
|
||||||
|
|
||||||
|
## Defining a Mock Class
|
||||||
|
|
||||||
|
### Mocking a Normal Class {#MockClass}
|
||||||
|
|
||||||
|
Given
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Foo {
|
||||||
|
public:
|
||||||
|
virtual ~Foo();
|
||||||
|
virtual int GetSize() const = 0;
|
||||||
|
virtual string Describe(const char* name) = 0;
|
||||||
|
virtual string Describe(int type) = 0;
|
||||||
|
virtual bool Process(Bar elem, int count) = 0;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
(note that `~Foo()` **must** be virtual) we can define its mock as
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
|
class MockFoo : public Foo {
|
||||||
|
public:
|
||||||
|
MOCK_METHOD(int, GetSize, (), (const, override));
|
||||||
|
MOCK_METHOD(string, Describe, (const char* name), (override));
|
||||||
|
MOCK_METHOD(string, Describe, (int type), (override));
|
||||||
|
MOCK_METHOD(bool, Process, (Bar elem, int count), (override));
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
To create a "nice" mock, which ignores all uninteresting calls, a "naggy" mock,
|
||||||
|
which warns on all uninteresting calls, or a "strict" mock, which treats them as
|
||||||
|
failures:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::NiceMock;
|
||||||
|
using ::testing::NaggyMock;
|
||||||
|
using ::testing::StrictMock;
|
||||||
|
|
||||||
|
NiceMock<MockFoo> nice_foo; // The type is a subclass of MockFoo.
|
||||||
|
NaggyMock<MockFoo> naggy_foo; // The type is a subclass of MockFoo.
|
||||||
|
StrictMock<MockFoo> strict_foo; // The type is a subclass of MockFoo.
|
||||||
|
```
|
||||||
|
|
||||||
|
{: .callout .note}
|
||||||
|
**Note:** A mock object is currently naggy by default. We may make it nice by
|
||||||
|
default in the future.
|
||||||
|
|
||||||
|
### Mocking a Class Template {#MockTemplate}
|
||||||
|
|
||||||
|
Class templates can be mocked just like any class.
|
||||||
|
|
||||||
|
To mock
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template <typename Elem>
|
||||||
|
class StackInterface {
|
||||||
|
public:
|
||||||
|
virtual ~StackInterface();
|
||||||
|
virtual int GetSize() const = 0;
|
||||||
|
virtual void Push(const Elem& x) = 0;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
(note that all member functions that are mocked, including `~StackInterface()`
|
||||||
|
**must** be virtual).
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template <typename Elem>
|
||||||
|
class MockStack : public StackInterface<Elem> {
|
||||||
|
public:
|
||||||
|
MOCK_METHOD(int, GetSize, (), (const, override));
|
||||||
|
MOCK_METHOD(void, Push, (const Elem& x), (override));
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Specifying Calling Conventions for Mock Functions
|
||||||
|
|
||||||
|
If your mock function doesn't use the default calling convention, you can
|
||||||
|
specify it by adding `Calltype(convention)` to `MOCK_METHOD`'s 4th parameter.
|
||||||
|
For example,
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
MOCK_METHOD(bool, Foo, (int n), (Calltype(STDMETHODCALLTYPE)));
|
||||||
|
MOCK_METHOD(int, Bar, (double x, double y),
|
||||||
|
(const, Calltype(STDMETHODCALLTYPE)));
|
||||||
|
```
|
||||||
|
|
||||||
|
where `STDMETHODCALLTYPE` is defined by `<objbase.h>` on Windows.
|
||||||
|
|
||||||
|
## Using Mocks in Tests {#UsingMocks}
|
||||||
|
|
||||||
|
The typical work flow is:
|
||||||
|
|
||||||
|
1. Import the gMock names you need to use. All gMock symbols are in the
|
||||||
|
`testing` namespace unless they are macros or otherwise noted.
|
||||||
|
2. Create the mock objects.
|
||||||
|
3. Optionally, set the default actions of the mock objects.
|
||||||
|
4. Set your expectations on the mock objects (How will they be called? What
|
||||||
|
will they do?).
|
||||||
|
5. Exercise code that uses the mock objects; if necessary, check the result
|
||||||
|
using googletest assertions.
|
||||||
|
6. When a mock object is destructed, gMock automatically verifies that all
|
||||||
|
expectations on it have been satisfied.
|
||||||
|
|
||||||
|
Here's an example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Return; // #1
|
||||||
|
|
||||||
|
TEST(BarTest, DoesThis) {
|
||||||
|
MockFoo foo; // #2
|
||||||
|
|
||||||
|
ON_CALL(foo, GetSize()) // #3
|
||||||
|
.WillByDefault(Return(1));
|
||||||
|
// ... other default actions ...
|
||||||
|
|
||||||
|
EXPECT_CALL(foo, Describe(5)) // #4
|
||||||
|
.Times(3)
|
||||||
|
.WillRepeatedly(Return("Category 5"));
|
||||||
|
// ... other expectations ...
|
||||||
|
|
||||||
|
EXPECT_EQ(MyProductionFunction(&foo), "good"); // #5
|
||||||
|
} // #6
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setting Default Actions {#OnCall}
|
||||||
|
|
||||||
|
gMock has a **built-in default action** for any function that returns `void`,
|
||||||
|
`bool`, a numeric value, or a pointer. In C++11, it will additionally returns
|
||||||
|
the default-constructed value, if one exists for the given type.
|
||||||
|
|
||||||
|
To customize the default action for functions with return type `T`, use
|
||||||
|
[`DefaultValue<T>`](reference/mocking.md#DefaultValue). For example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Sets the default action for return type std::unique_ptr<Buzz> to
|
||||||
|
// creating a new Buzz every time.
|
||||||
|
DefaultValue<std::unique_ptr<Buzz>>::SetFactory(
|
||||||
|
[] { return std::make_unique<Buzz>(AccessLevel::kInternal); });
|
||||||
|
|
||||||
|
// When this fires, the default action of MakeBuzz() will run, which
|
||||||
|
// will return a new Buzz object.
|
||||||
|
EXPECT_CALL(mock_buzzer_, MakeBuzz("hello")).Times(AnyNumber());
|
||||||
|
|
||||||
|
auto buzz1 = mock_buzzer_.MakeBuzz("hello");
|
||||||
|
auto buzz2 = mock_buzzer_.MakeBuzz("hello");
|
||||||
|
EXPECT_NE(buzz1, nullptr);
|
||||||
|
EXPECT_NE(buzz2, nullptr);
|
||||||
|
EXPECT_NE(buzz1, buzz2);
|
||||||
|
|
||||||
|
// Resets the default action for return type std::unique_ptr<Buzz>,
|
||||||
|
// to avoid interfere with other tests.
|
||||||
|
DefaultValue<std::unique_ptr<Buzz>>::Clear();
|
||||||
|
```
|
||||||
|
|
||||||
|
To customize the default action for a particular method of a specific mock
|
||||||
|
object, use [`ON_CALL`](reference/mocking.md#ON_CALL). `ON_CALL` has a similar
|
||||||
|
syntax to `EXPECT_CALL`, but it is used for setting default behaviors when you
|
||||||
|
do not require that the mock method is called. See
|
||||||
|
[Knowing When to Expect](gmock_cook_book.md#UseOnCall) for a more detailed
|
||||||
|
discussion.
|
||||||
|
|
||||||
|
## Setting Expectations {#ExpectCall}
|
||||||
|
|
||||||
|
See [`EXPECT_CALL`](reference/mocking.md#EXPECT_CALL) in the Mocking Reference.
|
||||||
|
|
||||||
|
## Matchers {#MatcherList}
|
||||||
|
|
||||||
|
See the [Matchers Reference](reference/matchers.md).
|
||||||
|
|
||||||
|
## Actions {#ActionList}
|
||||||
|
|
||||||
|
See the [Actions Reference](reference/actions.md).
|
||||||
|
|
||||||
|
## Cardinalities {#CardinalityList}
|
||||||
|
|
||||||
|
See the [`Times` clause](reference/mocking.md#EXPECT_CALL.Times) of
|
||||||
|
`EXPECT_CALL` in the Mocking Reference.
|
||||||
|
|
||||||
|
## Expectation Order
|
||||||
|
|
||||||
|
By default, expectations can be matched in *any* order. If some or all
|
||||||
|
expectations must be matched in a given order, you can use the
|
||||||
|
[`After` clause](reference/mocking.md#EXPECT_CALL.After) or
|
||||||
|
[`InSequence` clause](reference/mocking.md#EXPECT_CALL.InSequence) of
|
||||||
|
`EXPECT_CALL`, or use an [`InSequence` object](reference/mocking.md#InSequence).
|
||||||
|
|
||||||
|
## Verifying and Resetting a Mock
|
||||||
|
|
||||||
|
gMock will verify the expectations on a mock object when it is destructed, or
|
||||||
|
you can do it earlier:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Mock;
|
||||||
|
...
|
||||||
|
// Verifies and removes the expectations on mock_obj;
|
||||||
|
// returns true if and only if successful.
|
||||||
|
Mock::VerifyAndClearExpectations(&mock_obj);
|
||||||
|
...
|
||||||
|
// Verifies and removes the expectations on mock_obj;
|
||||||
|
// also removes the default actions set by ON_CALL();
|
||||||
|
// returns true if and only if successful.
|
||||||
|
Mock::VerifyAndClear(&mock_obj);
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not set new expectations after verifying and clearing a mock after its use.
|
||||||
|
Setting expectations after code that exercises the mock has undefined behavior.
|
||||||
|
See [Using Mocks in Tests](gmock_for_dummies.md#using-mocks-in-tests) for more
|
||||||
|
information.
|
||||||
|
|
||||||
|
You can also tell gMock that a mock object can be leaked and doesn't need to be
|
||||||
|
verified:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Mock::AllowLeak(&mock_obj);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mock Classes
|
||||||
|
|
||||||
|
gMock defines a convenient mock class template
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class MockFunction<R(A1, ..., An)> {
|
||||||
|
public:
|
||||||
|
MOCK_METHOD(R, Call, (A1, ..., An));
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
See this [recipe](gmock_cook_book.md#UsingCheckPoints) for one application of
|
||||||
|
it.
|
||||||
|
|
||||||
|
## Flags
|
||||||
|
|
||||||
|
| Flag | Description |
|
||||||
|
| :----------------------------- | :---------------------------------------- |
|
||||||
|
| `--gmock_catch_leaked_mocks=0` | Don't report leaked mock objects as failures. |
|
||||||
|
| `--gmock_verbose=LEVEL` | Sets the default verbosity level (`info`, `warning`, or `error`) of Google Mock messages. |
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,390 @@
|
||||||
|
# Legacy gMock FAQ
|
||||||
|
|
||||||
|
### When I call a method on my mock object, the method for the real object is invoked instead. What's the problem?
|
||||||
|
|
||||||
|
In order for a method to be mocked, it must be *virtual*, unless you use the
|
||||||
|
[high-perf dependency injection technique](gmock_cook_book.md#MockingNonVirtualMethods).
|
||||||
|
|
||||||
|
### Can I mock a variadic function?
|
||||||
|
|
||||||
|
You cannot mock a variadic function (i.e. a function taking ellipsis (`...`)
|
||||||
|
arguments) directly in gMock.
|
||||||
|
|
||||||
|
The problem is that in general, there is *no way* for a mock object to know how
|
||||||
|
many arguments are passed to the variadic method, and what the arguments' types
|
||||||
|
are. Only the *author of the base class* knows the protocol, and we cannot look
|
||||||
|
into his or her head.
|
||||||
|
|
||||||
|
Therefore, to mock such a function, the *user* must teach the mock object how to
|
||||||
|
figure out the number of arguments and their types. One way to do it is to
|
||||||
|
provide overloaded versions of the function.
|
||||||
|
|
||||||
|
Ellipsis arguments are inherited from C and not really a C++ feature. They are
|
||||||
|
unsafe to use and don't work with arguments that have constructors or
|
||||||
|
destructors. Therefore we recommend to avoid them in C++ as much as possible.
|
||||||
|
|
||||||
|
### MSVC gives me warning C4301 or C4373 when I define a mock method with a const parameter. Why?
|
||||||
|
|
||||||
|
If you compile this using Microsoft Visual C++ 2005 SP1:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Foo {
|
||||||
|
...
|
||||||
|
virtual void Bar(const int i) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MockFoo : public Foo {
|
||||||
|
...
|
||||||
|
MOCK_METHOD(void, Bar, (const int i), (override));
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
You may get the following warning:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
warning C4301: 'MockFoo::Bar': overriding virtual function only differs from 'Foo::Bar' by const/volatile qualifier
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a MSVC bug. The same code compiles fine with gcc, for example. If you
|
||||||
|
use Visual C++ 2008 SP1, you would get the warning:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
warning C4373: 'MockFoo::Bar': virtual function overrides 'Foo::Bar', previous versions of the compiler did not override when parameters only differed by const/volatile qualifiers
|
||||||
|
```
|
||||||
|
|
||||||
|
In C++, if you *declare* a function with a `const` parameter, the `const`
|
||||||
|
modifier is ignored. Therefore, the `Foo` base class above is equivalent to:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Foo {
|
||||||
|
...
|
||||||
|
virtual void Bar(int i) = 0; // int or const int? Makes no difference.
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
In fact, you can *declare* `Bar()` with an `int` parameter, and define it with a
|
||||||
|
`const int` parameter. The compiler will still match them up.
|
||||||
|
|
||||||
|
Since making a parameter `const` is meaningless in the method declaration, we
|
||||||
|
recommend to remove it in both `Foo` and `MockFoo`. That should workaround the
|
||||||
|
VC bug.
|
||||||
|
|
||||||
|
Note that we are talking about the *top-level* `const` modifier here. If the
|
||||||
|
function parameter is passed by pointer or reference, declaring the pointee or
|
||||||
|
referee as `const` is still meaningful. For example, the following two
|
||||||
|
declarations are *not* equivalent:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void Bar(int* p); // Neither p nor *p is const.
|
||||||
|
void Bar(const int* p); // p is not const, but *p is.
|
||||||
|
```
|
||||||
|
|
||||||
|
### I can't figure out why gMock thinks my expectations are not satisfied. What should I do?
|
||||||
|
|
||||||
|
You might want to run your test with `--gmock_verbose=info`. This flag lets
|
||||||
|
gMock print a trace of every mock function call it receives. By studying the
|
||||||
|
trace, you'll gain insights on why the expectations you set are not met.
|
||||||
|
|
||||||
|
If you see the message "The mock function has no default action set, and its
|
||||||
|
return type has no default value set.", then try
|
||||||
|
[adding a default action](gmock_cheat_sheet.md#OnCall). Due to a known issue,
|
||||||
|
unexpected calls on mocks without default actions don't print out a detailed
|
||||||
|
comparison between the actual arguments and the expected arguments.
|
||||||
|
|
||||||
|
### My program crashed and `ScopedMockLog` spit out tons of messages. Is it a gMock bug?
|
||||||
|
|
||||||
|
gMock and `ScopedMockLog` are likely doing the right thing here.
|
||||||
|
|
||||||
|
When a test crashes, the failure signal handler will try to log a lot of
|
||||||
|
information (the stack trace, and the address map, for example). The messages
|
||||||
|
are compounded if you have many threads with depth stacks. When `ScopedMockLog`
|
||||||
|
intercepts these messages and finds that they don't match any expectations, it
|
||||||
|
prints an error for each of them.
|
||||||
|
|
||||||
|
You can learn to ignore the errors, or you can rewrite your expectations to make
|
||||||
|
your test more robust, for example, by adding something like:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::AnyNumber;
|
||||||
|
using ::testing::Not;
|
||||||
|
...
|
||||||
|
// Ignores any log not done by us.
|
||||||
|
EXPECT_CALL(log, Log(_, Not(EndsWith("/my_file.cc")), _))
|
||||||
|
.Times(AnyNumber());
|
||||||
|
```
|
||||||
|
|
||||||
|
### How can I assert that a function is NEVER called?
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::_;
|
||||||
|
...
|
||||||
|
EXPECT_CALL(foo, Bar(_))
|
||||||
|
.Times(0);
|
||||||
|
```
|
||||||
|
|
||||||
|
### I have a failed test where gMock tells me TWICE that a particular expectation is not satisfied. Isn't this redundant?
|
||||||
|
|
||||||
|
When gMock detects a failure, it prints relevant information (the mock function
|
||||||
|
arguments, the state of relevant expectations, and etc) to help the user debug.
|
||||||
|
If another failure is detected, gMock will do the same, including printing the
|
||||||
|
state of relevant expectations.
|
||||||
|
|
||||||
|
Sometimes an expectation's state didn't change between two failures, and you'll
|
||||||
|
see the same description of the state twice. They are however *not* redundant,
|
||||||
|
as they refer to *different points in time*. The fact they are the same *is*
|
||||||
|
interesting information.
|
||||||
|
|
||||||
|
### I get a heapcheck failure when using a mock object, but using a real object is fine. What can be wrong?
|
||||||
|
|
||||||
|
Does the class (hopefully a pure interface) you are mocking have a virtual
|
||||||
|
destructor?
|
||||||
|
|
||||||
|
Whenever you derive from a base class, make sure its destructor is virtual.
|
||||||
|
Otherwise Bad Things will happen. Consider the following code:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Base {
|
||||||
|
public:
|
||||||
|
// Not virtual, but should be.
|
||||||
|
~Base() { ... }
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
|
class Derived : public Base {
|
||||||
|
public:
|
||||||
|
...
|
||||||
|
private:
|
||||||
|
std::string value_;
|
||||||
|
};
|
||||||
|
|
||||||
|
...
|
||||||
|
Base* p = new Derived;
|
||||||
|
...
|
||||||
|
delete p; // Surprise! ~Base() will be called, but ~Derived() will not
|
||||||
|
// - value_ is leaked.
|
||||||
|
```
|
||||||
|
|
||||||
|
By changing `~Base()` to virtual, `~Derived()` will be correctly called when
|
||||||
|
`delete p` is executed, and the heap checker will be happy.
|
||||||
|
|
||||||
|
### The "newer expectations override older ones" rule makes writing expectations awkward. Why does gMock do that?
|
||||||
|
|
||||||
|
When people complain about this, often they are referring to code like:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Return;
|
||||||
|
...
|
||||||
|
// foo.Bar() should be called twice, return 1 the first time, and return
|
||||||
|
// 2 the second time. However, I have to write the expectations in the
|
||||||
|
// reverse order. This sucks big time!!!
|
||||||
|
EXPECT_CALL(foo, Bar())
|
||||||
|
.WillOnce(Return(2))
|
||||||
|
.RetiresOnSaturation();
|
||||||
|
EXPECT_CALL(foo, Bar())
|
||||||
|
.WillOnce(Return(1))
|
||||||
|
.RetiresOnSaturation();
|
||||||
|
```
|
||||||
|
|
||||||
|
The problem, is that they didn't pick the **best** way to express the test's
|
||||||
|
intent.
|
||||||
|
|
||||||
|
By default, expectations don't have to be matched in *any* particular order. If
|
||||||
|
you want them to match in a certain order, you need to be explicit. This is
|
||||||
|
gMock's (and jMock's) fundamental philosophy: it's easy to accidentally
|
||||||
|
over-specify your tests, and we want to make it harder to do so.
|
||||||
|
|
||||||
|
There are two better ways to write the test spec. You could either put the
|
||||||
|
expectations in sequence:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Return;
|
||||||
|
...
|
||||||
|
// foo.Bar() should be called twice, return 1 the first time, and return
|
||||||
|
// 2 the second time. Using a sequence, we can write the expectations
|
||||||
|
// in their natural order.
|
||||||
|
{
|
||||||
|
InSequence s;
|
||||||
|
EXPECT_CALL(foo, Bar())
|
||||||
|
.WillOnce(Return(1))
|
||||||
|
.RetiresOnSaturation();
|
||||||
|
EXPECT_CALL(foo, Bar())
|
||||||
|
.WillOnce(Return(2))
|
||||||
|
.RetiresOnSaturation();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
or you can put the sequence of actions in the same expectation:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Return;
|
||||||
|
...
|
||||||
|
// foo.Bar() should be called twice, return 1 the first time, and return
|
||||||
|
// 2 the second time.
|
||||||
|
EXPECT_CALL(foo, Bar())
|
||||||
|
.WillOnce(Return(1))
|
||||||
|
.WillOnce(Return(2))
|
||||||
|
.RetiresOnSaturation();
|
||||||
|
```
|
||||||
|
|
||||||
|
Back to the original questions: why does gMock search the expectations (and
|
||||||
|
`ON_CALL`s) from back to front? Because this allows a user to set up a mock's
|
||||||
|
behavior for the common case early (e.g. in the mock's constructor or the test
|
||||||
|
fixture's set-up phase) and customize it with more specific rules later. If
|
||||||
|
gMock searches from front to back, this very useful pattern won't be possible.
|
||||||
|
|
||||||
|
### gMock prints a warning when a function without EXPECT_CALL is called, even if I have set its behavior using ON_CALL. Would it be reasonable not to show the warning in this case?
|
||||||
|
|
||||||
|
When choosing between being neat and being safe, we lean toward the latter. So
|
||||||
|
the answer is that we think it's better to show the warning.
|
||||||
|
|
||||||
|
Often people write `ON_CALL`s in the mock object's constructor or `SetUp()`, as
|
||||||
|
the default behavior rarely changes from test to test. Then in the test body
|
||||||
|
they set the expectations, which are often different for each test. Having an
|
||||||
|
`ON_CALL` in the set-up part of a test doesn't mean that the calls are expected.
|
||||||
|
If there's no `EXPECT_CALL` and the method is called, it's possibly an error. If
|
||||||
|
we quietly let the call go through without notifying the user, bugs may creep in
|
||||||
|
unnoticed.
|
||||||
|
|
||||||
|
If, however, you are sure that the calls are OK, you can write
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::_;
|
||||||
|
...
|
||||||
|
EXPECT_CALL(foo, Bar(_))
|
||||||
|
.WillRepeatedly(...);
|
||||||
|
```
|
||||||
|
|
||||||
|
instead of
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::_;
|
||||||
|
...
|
||||||
|
ON_CALL(foo, Bar(_))
|
||||||
|
.WillByDefault(...);
|
||||||
|
```
|
||||||
|
|
||||||
|
This tells gMock that you do expect the calls and no warning should be printed.
|
||||||
|
|
||||||
|
Also, you can control the verbosity by specifying `--gmock_verbose=error`. Other
|
||||||
|
values are `info` and `warning`. If you find the output too noisy when
|
||||||
|
debugging, just choose a less verbose level.
|
||||||
|
|
||||||
|
### How can I delete the mock function's argument in an action?
|
||||||
|
|
||||||
|
If your mock function takes a pointer argument and you want to delete that
|
||||||
|
argument, you can use testing::DeleteArg<N>() to delete the N'th (zero-indexed)
|
||||||
|
argument:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::_;
|
||||||
|
...
|
||||||
|
MOCK_METHOD(void, Bar, (X* x, const Y& y));
|
||||||
|
...
|
||||||
|
EXPECT_CALL(mock_foo_, Bar(_, _))
|
||||||
|
.WillOnce(testing::DeleteArg<0>()));
|
||||||
|
```
|
||||||
|
|
||||||
|
### How can I perform an arbitrary action on a mock function's argument?
|
||||||
|
|
||||||
|
If you find yourself needing to perform some action that's not supported by
|
||||||
|
gMock directly, remember that you can define your own actions using
|
||||||
|
[`MakeAction()`](#NewMonoActions) or
|
||||||
|
[`MakePolymorphicAction()`](#NewPolyActions), or you can write a stub function
|
||||||
|
and invoke it using [`Invoke()`](#FunctionsAsActions).
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::_;
|
||||||
|
using ::testing::Invoke;
|
||||||
|
...
|
||||||
|
MOCK_METHOD(void, Bar, (X* p));
|
||||||
|
...
|
||||||
|
EXPECT_CALL(mock_foo_, Bar(_))
|
||||||
|
.WillOnce(Invoke(MyAction(...)));
|
||||||
|
```
|
||||||
|
|
||||||
|
### My code calls a static/global function. Can I mock it?
|
||||||
|
|
||||||
|
You can, but you need to make some changes.
|
||||||
|
|
||||||
|
In general, if you find yourself needing to mock a static function, it's a sign
|
||||||
|
that your modules are too tightly coupled (and less flexible, less reusable,
|
||||||
|
less testable, etc). You are probably better off defining a small interface and
|
||||||
|
call the function through that interface, which then can be easily mocked. It's
|
||||||
|
a bit of work initially, but usually pays for itself quickly.
|
||||||
|
|
||||||
|
This Google Testing Blog
|
||||||
|
[post](https://testing.googleblog.com/2008/06/defeat-static-cling.html) says it
|
||||||
|
excellently. Check it out.
|
||||||
|
|
||||||
|
### My mock object needs to do complex stuff. It's a lot of pain to specify the actions. gMock sucks!
|
||||||
|
|
||||||
|
I know it's not a question, but you get an answer for free any way. :-)
|
||||||
|
|
||||||
|
With gMock, you can create mocks in C++ easily. And people might be tempted to
|
||||||
|
use them everywhere. Sometimes they work great, and sometimes you may find them,
|
||||||
|
well, a pain to use. So, what's wrong in the latter case?
|
||||||
|
|
||||||
|
When you write a test without using mocks, you exercise the code and assert that
|
||||||
|
it returns the correct value or that the system is in an expected state. This is
|
||||||
|
sometimes called "state-based testing".
|
||||||
|
|
||||||
|
Mocks are great for what some call "interaction-based" testing: instead of
|
||||||
|
checking the system state at the very end, mock objects verify that they are
|
||||||
|
invoked the right way and report an error as soon as it arises, giving you a
|
||||||
|
handle on the precise context in which the error was triggered. This is often
|
||||||
|
more effective and economical to do than state-based testing.
|
||||||
|
|
||||||
|
If you are doing state-based testing and using a test double just to simulate
|
||||||
|
the real object, you are probably better off using a fake. Using a mock in this
|
||||||
|
case causes pain, as it's not a strong point for mocks to perform complex
|
||||||
|
actions. If you experience this and think that mocks suck, you are just not
|
||||||
|
using the right tool for your problem. Or, you might be trying to solve the
|
||||||
|
wrong problem. :-)
|
||||||
|
|
||||||
|
### I got a warning "Uninteresting function call encountered - default action taken.." Should I panic?
|
||||||
|
|
||||||
|
By all means, NO! It's just an FYI. :-)
|
||||||
|
|
||||||
|
What it means is that you have a mock function, you haven't set any expectations
|
||||||
|
on it (by gMock's rule this means that you are not interested in calls to this
|
||||||
|
function and therefore it can be called any number of times), and it is called.
|
||||||
|
That's OK - you didn't say it's not OK to call the function!
|
||||||
|
|
||||||
|
What if you actually meant to disallow this function to be called, but forgot to
|
||||||
|
write `EXPECT_CALL(foo, Bar()).Times(0)`? While one can argue that it's the
|
||||||
|
user's fault, gMock tries to be nice and prints you a note.
|
||||||
|
|
||||||
|
So, when you see the message and believe that there shouldn't be any
|
||||||
|
uninteresting calls, you should investigate what's going on. To make your life
|
||||||
|
easier, gMock dumps the stack trace when an uninteresting call is encountered.
|
||||||
|
From that you can figure out which mock function it is, and how it is called.
|
||||||
|
|
||||||
|
### I want to define a custom action. Should I use Invoke() or implement the ActionInterface interface?
|
||||||
|
|
||||||
|
Either way is fine - you want to choose the one that's more convenient for your
|
||||||
|
circumstance.
|
||||||
|
|
||||||
|
Usually, if your action is for a particular function type, defining it using
|
||||||
|
`Invoke()` should be easier; if your action can be used in functions of
|
||||||
|
different types (e.g. if you are defining `Return(*value*)`),
|
||||||
|
`MakePolymorphicAction()` is easiest. Sometimes you want precise control on what
|
||||||
|
types of functions the action can be used in, and implementing `ActionInterface`
|
||||||
|
is the way to go here. See the implementation of `Return()` in `gmock-actions.h`
|
||||||
|
for an example.
|
||||||
|
|
||||||
|
### I use SetArgPointee() in WillOnce(), but gcc complains about "conflicting return type specified". What does it mean?
|
||||||
|
|
||||||
|
You got this error as gMock has no idea what value it should return when the
|
||||||
|
mock method is called. `SetArgPointee()` says what the side effect is, but
|
||||||
|
doesn't say what the return value should be. You need `DoAll()` to chain a
|
||||||
|
`SetArgPointee()` with a `Return()` that provides a value appropriate to the API
|
||||||
|
being mocked.
|
||||||
|
|
||||||
|
See this [recipe](gmock_cook_book.md#mocking-side-effects) for more details and
|
||||||
|
an example.
|
||||||
|
|
||||||
|
### I have a huge mock class, and Microsoft Visual C++ runs out of memory when compiling it. What can I do?
|
||||||
|
|
||||||
|
We've noticed that when the `/clr` compiler flag is used, Visual C++ uses 5~6
|
||||||
|
times as much memory when compiling a mock class. We suggest to avoid `/clr`
|
||||||
|
when compiling native C++ mocks.
|
|
@ -0,0 +1,700 @@
|
||||||
|
# gMock for Dummies
|
||||||
|
|
||||||
|
## What Is gMock?
|
||||||
|
|
||||||
|
When you write a prototype or test, often it's not feasible or wise to rely on
|
||||||
|
real objects entirely. A **mock object** implements the same interface as a real
|
||||||
|
object (so it can be used as one), but lets you specify at run time how it will
|
||||||
|
be used and what it should do (which methods will be called? in which order? how
|
||||||
|
many times? with what arguments? what will they return? etc).
|
||||||
|
|
||||||
|
It is easy to confuse the term *fake objects* with mock objects. Fakes and mocks
|
||||||
|
actually mean very different things in the Test-Driven Development (TDD)
|
||||||
|
community:
|
||||||
|
|
||||||
|
* **Fake** objects have working implementations, but usually take some
|
||||||
|
shortcut (perhaps to make the operations less expensive), which makes them
|
||||||
|
not suitable for production. An in-memory file system would be an example of
|
||||||
|
a fake.
|
||||||
|
* **Mocks** are objects pre-programmed with *expectations*, which form a
|
||||||
|
specification of the calls they are expected to receive.
|
||||||
|
|
||||||
|
If all this seems too abstract for you, don't worry - the most important thing
|
||||||
|
to remember is that a mock allows you to check the *interaction* between itself
|
||||||
|
and code that uses it. The difference between fakes and mocks shall become much
|
||||||
|
clearer once you start to use mocks.
|
||||||
|
|
||||||
|
**gMock** is a library (sometimes we also call it a "framework" to make it sound
|
||||||
|
cool) for creating mock classes and using them. It does to C++ what
|
||||||
|
jMock/EasyMock does to Java (well, more or less).
|
||||||
|
|
||||||
|
When using gMock,
|
||||||
|
|
||||||
|
1. first, you use some simple macros to describe the interface you want to
|
||||||
|
mock, and they will expand to the implementation of your mock class;
|
||||||
|
2. next, you create some mock objects and specify its expectations and behavior
|
||||||
|
using an intuitive syntax;
|
||||||
|
3. then you exercise code that uses the mock objects. gMock will catch any
|
||||||
|
violation to the expectations as soon as it arises.
|
||||||
|
|
||||||
|
## Why gMock?
|
||||||
|
|
||||||
|
While mock objects help you remove unnecessary dependencies in tests and make
|
||||||
|
them fast and reliable, using mocks manually in C++ is *hard*:
|
||||||
|
|
||||||
|
* Someone has to implement the mocks. The job is usually tedious and
|
||||||
|
error-prone. No wonder people go great distance to avoid it.
|
||||||
|
* The quality of those manually written mocks is a bit, uh, unpredictable. You
|
||||||
|
may see some really polished ones, but you may also see some that were
|
||||||
|
hacked up in a hurry and have all sorts of ad hoc restrictions.
|
||||||
|
* The knowledge you gained from using one mock doesn't transfer to the next
|
||||||
|
one.
|
||||||
|
|
||||||
|
In contrast, Java and Python programmers have some fine mock frameworks (jMock,
|
||||||
|
EasyMock, etc), which automate the creation of mocks. As a result, mocking is a
|
||||||
|
proven effective technique and widely adopted practice in those communities.
|
||||||
|
Having the right tool absolutely makes the difference.
|
||||||
|
|
||||||
|
gMock was built to help C++ programmers. It was inspired by jMock and EasyMock,
|
||||||
|
but designed with C++'s specifics in mind. It is your friend if any of the
|
||||||
|
following problems is bothering you:
|
||||||
|
|
||||||
|
* You are stuck with a sub-optimal design and wish you had done more
|
||||||
|
prototyping before it was too late, but prototyping in C++ is by no means
|
||||||
|
"rapid".
|
||||||
|
* Your tests are slow as they depend on too many libraries or use expensive
|
||||||
|
resources (e.g. a database).
|
||||||
|
* Your tests are brittle as some resources they use are unreliable (e.g. the
|
||||||
|
network).
|
||||||
|
* You want to test how your code handles a failure (e.g. a file checksum
|
||||||
|
error), but it's not easy to cause one.
|
||||||
|
* You need to make sure that your module interacts with other modules in the
|
||||||
|
right way, but it's hard to observe the interaction; therefore you resort to
|
||||||
|
observing the side effects at the end of the action, but it's awkward at
|
||||||
|
best.
|
||||||
|
* You want to "mock out" your dependencies, except that they don't have mock
|
||||||
|
implementations yet; and, frankly, you aren't thrilled by some of those
|
||||||
|
hand-written mocks.
|
||||||
|
|
||||||
|
We encourage you to use gMock as
|
||||||
|
|
||||||
|
* a *design* tool, for it lets you experiment with your interface design early
|
||||||
|
and often. More iterations lead to better designs!
|
||||||
|
* a *testing* tool to cut your tests' outbound dependencies and probe the
|
||||||
|
interaction between your module and its collaborators.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
gMock is bundled with googletest.
|
||||||
|
|
||||||
|
## A Case for Mock Turtles
|
||||||
|
|
||||||
|
Let's look at an example. Suppose you are developing a graphics program that
|
||||||
|
relies on a [LOGO](http://en.wikipedia.org/wiki/Logo_programming_language)-like
|
||||||
|
API for drawing. How would you test that it does the right thing? Well, you can
|
||||||
|
run it and compare the screen with a golden screen snapshot, but let's admit it:
|
||||||
|
tests like this are expensive to run and fragile (What if you just upgraded to a
|
||||||
|
shiny new graphics card that has better anti-aliasing? Suddenly you have to
|
||||||
|
update all your golden images.). It would be too painful if all your tests are
|
||||||
|
like this. Fortunately, you learned about
|
||||||
|
[Dependency Injection](http://en.wikipedia.org/wiki/Dependency_injection) and know the right thing
|
||||||
|
to do: instead of having your application talk to the system API directly, wrap
|
||||||
|
the API in an interface (say, `Turtle`) and code to that interface:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class Turtle {
|
||||||
|
...
|
||||||
|
virtual ~Turtle() {}
|
||||||
|
virtual void PenUp() = 0;
|
||||||
|
virtual void PenDown() = 0;
|
||||||
|
virtual void Forward(int distance) = 0;
|
||||||
|
virtual void Turn(int degrees) = 0;
|
||||||
|
virtual void GoTo(int x, int y) = 0;
|
||||||
|
virtual int GetX() const = 0;
|
||||||
|
virtual int GetY() const = 0;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
(Note that the destructor of `Turtle` **must** be virtual, as is the case for
|
||||||
|
**all** classes you intend to inherit from - otherwise the destructor of the
|
||||||
|
derived class will not be called when you delete an object through a base
|
||||||
|
pointer, and you'll get corrupted program states like memory leaks.)
|
||||||
|
|
||||||
|
You can control whether the turtle's movement will leave a trace using `PenUp()`
|
||||||
|
and `PenDown()`, and control its movement using `Forward()`, `Turn()`, and
|
||||||
|
`GoTo()`. Finally, `GetX()` and `GetY()` tell you the current position of the
|
||||||
|
turtle.
|
||||||
|
|
||||||
|
Your program will normally use a real implementation of this interface. In
|
||||||
|
tests, you can use a mock implementation instead. This allows you to easily
|
||||||
|
check what drawing primitives your program is calling, with what arguments, and
|
||||||
|
in which order. Tests written this way are much more robust (they won't break
|
||||||
|
because your new machine does anti-aliasing differently), easier to read and
|
||||||
|
maintain (the intent of a test is expressed in the code, not in some binary
|
||||||
|
images), and run *much, much faster*.
|
||||||
|
|
||||||
|
## Writing the Mock Class
|
||||||
|
|
||||||
|
If you are lucky, the mocks you need to use have already been implemented by
|
||||||
|
some nice people. If, however, you find yourself in the position to write a mock
|
||||||
|
class, relax - gMock turns this task into a fun game! (Well, almost.)
|
||||||
|
|
||||||
|
### How to Define It
|
||||||
|
|
||||||
|
Using the `Turtle` interface as example, here are the simple steps you need to
|
||||||
|
follow:
|
||||||
|
|
||||||
|
* Derive a class `MockTurtle` from `Turtle`.
|
||||||
|
* Take a *virtual* function of `Turtle` (while it's possible to
|
||||||
|
[mock non-virtual methods using templates](gmock_cook_book.md#MockingNonVirtualMethods),
|
||||||
|
it's much more involved).
|
||||||
|
* In the `public:` section of the child class, write `MOCK_METHOD();`
|
||||||
|
* Now comes the fun part: you take the function signature, cut-and-paste it
|
||||||
|
into the macro, and add two commas - one between the return type and the
|
||||||
|
name, another between the name and the argument list.
|
||||||
|
* If you're mocking a const method, add a 4th parameter containing `(const)`
|
||||||
|
(the parentheses are required).
|
||||||
|
* Since you're overriding a virtual method, we suggest adding the `override`
|
||||||
|
keyword. For const methods the 4th parameter becomes `(const, override)`,
|
||||||
|
for non-const methods just `(override)`. This isn't mandatory.
|
||||||
|
* Repeat until all virtual functions you want to mock are done. (It goes
|
||||||
|
without saying that *all* pure virtual methods in your abstract class must
|
||||||
|
be either mocked or overridden.)
|
||||||
|
|
||||||
|
After the process, you should have something like:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <gmock/gmock.h> // Brings in gMock.
|
||||||
|
|
||||||
|
class MockTurtle : public Turtle {
|
||||||
|
public:
|
||||||
|
...
|
||||||
|
MOCK_METHOD(void, PenUp, (), (override));
|
||||||
|
MOCK_METHOD(void, PenDown, (), (override));
|
||||||
|
MOCK_METHOD(void, Forward, (int distance), (override));
|
||||||
|
MOCK_METHOD(void, Turn, (int degrees), (override));
|
||||||
|
MOCK_METHOD(void, GoTo, (int x, int y), (override));
|
||||||
|
MOCK_METHOD(int, GetX, (), (const, override));
|
||||||
|
MOCK_METHOD(int, GetY, (), (const, override));
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
You don't need to define these mock methods somewhere else - the `MOCK_METHOD`
|
||||||
|
macro will generate the definitions for you. It's that simple!
|
||||||
|
|
||||||
|
### Where to Put It
|
||||||
|
|
||||||
|
When you define a mock class, you need to decide where to put its definition.
|
||||||
|
Some people put it in a `_test.cc`. This is fine when the interface being mocked
|
||||||
|
(say, `Foo`) is owned by the same person or team. Otherwise, when the owner of
|
||||||
|
`Foo` changes it, your test could break. (You can't really expect `Foo`'s
|
||||||
|
maintainer to fix every test that uses `Foo`, can you?)
|
||||||
|
|
||||||
|
Generally, you should not mock classes you don't own. If you must mock such a
|
||||||
|
class owned by others, define the mock class in `Foo`'s Bazel package (usually
|
||||||
|
the same directory or a `testing` sub-directory), and put it in a `.h` and a
|
||||||
|
`cc_library` with `testonly=True`. Then everyone can reference them from their
|
||||||
|
tests. If `Foo` ever changes, there is only one copy of `MockFoo` to change, and
|
||||||
|
only tests that depend on the changed methods need to be fixed.
|
||||||
|
|
||||||
|
Another way to do it: you can introduce a thin layer `FooAdaptor` on top of
|
||||||
|
`Foo` and code to this new interface. Since you own `FooAdaptor`, you can absorb
|
||||||
|
changes in `Foo` much more easily. While this is more work initially, carefully
|
||||||
|
choosing the adaptor interface can make your code easier to write and more
|
||||||
|
readable (a net win in the long run), as you can choose `FooAdaptor` to fit your
|
||||||
|
specific domain much better than `Foo` does.
|
||||||
|
|
||||||
|
## Using Mocks in Tests
|
||||||
|
|
||||||
|
Once you have a mock class, using it is easy. The typical work flow is:
|
||||||
|
|
||||||
|
1. Import the gMock names from the `testing` namespace such that you can use
|
||||||
|
them unqualified (You only have to do it once per file). Remember that
|
||||||
|
namespaces are a good idea.
|
||||||
|
2. Create some mock objects.
|
||||||
|
3. Specify your expectations on them (How many times will a method be called?
|
||||||
|
With what arguments? What should it do? etc.).
|
||||||
|
4. Exercise some code that uses the mocks; optionally, check the result using
|
||||||
|
googletest assertions. If a mock method is called more than expected or with
|
||||||
|
wrong arguments, you'll get an error immediately.
|
||||||
|
5. When a mock is destructed, gMock will automatically check whether all
|
||||||
|
expectations on it have been satisfied.
|
||||||
|
|
||||||
|
Here's an example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "path/to/mock-turtle.h"
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
using ::testing::AtLeast; // #1
|
||||||
|
|
||||||
|
TEST(PainterTest, CanDrawSomething) {
|
||||||
|
MockTurtle turtle; // #2
|
||||||
|
EXPECT_CALL(turtle, PenDown()) // #3
|
||||||
|
.Times(AtLeast(1));
|
||||||
|
|
||||||
|
Painter painter(&turtle); // #4
|
||||||
|
|
||||||
|
EXPECT_TRUE(painter.DrawCircle(0, 0, 10)); // #5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
As you might have guessed, this test checks that `PenDown()` is called at least
|
||||||
|
once. If the `painter` object didn't call this method, your test will fail with
|
||||||
|
a message like this:
|
||||||
|
|
||||||
|
```text
|
||||||
|
path/to/my_test.cc:119: Failure
|
||||||
|
Actual function call count doesn't match this expectation:
|
||||||
|
Actually: never called;
|
||||||
|
Expected: called at least once.
|
||||||
|
Stack trace:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tip 1:** If you run the test from an Emacs buffer, you can hit `<Enter>` on
|
||||||
|
the line number to jump right to the failed expectation.
|
||||||
|
|
||||||
|
**Tip 2:** If your mock objects are never deleted, the final verification won't
|
||||||
|
happen. Therefore it's a good idea to turn on the heap checker in your tests
|
||||||
|
when you allocate mocks on the heap. You get that automatically if you use the
|
||||||
|
`gtest_main` library already.
|
||||||
|
|
||||||
|
**Important note:** gMock requires expectations to be set **before** the mock
|
||||||
|
functions are called, otherwise the behavior is **undefined**. Do not alternate
|
||||||
|
between calls to `EXPECT_CALL()` and calls to the mock functions, and do not set
|
||||||
|
any expectations on a mock after passing the mock to an API.
|
||||||
|
|
||||||
|
This means `EXPECT_CALL()` should be read as expecting that a call will occur
|
||||||
|
*in the future*, not that a call has occurred. Why does gMock work like that?
|
||||||
|
Well, specifying the expectation beforehand allows gMock to report a violation
|
||||||
|
as soon as it rises, when the context (stack trace, etc) is still available.
|
||||||
|
This makes debugging much easier.
|
||||||
|
|
||||||
|
Admittedly, this test is contrived and doesn't do much. You can easily achieve
|
||||||
|
the same effect without using gMock. However, as we shall reveal soon, gMock
|
||||||
|
allows you to do *so much more* with the mocks.
|
||||||
|
|
||||||
|
## Setting Expectations
|
||||||
|
|
||||||
|
The key to using a mock object successfully is to set the *right expectations*
|
||||||
|
on it. If you set the expectations too strict, your test will fail as the result
|
||||||
|
of unrelated changes. If you set them too loose, bugs can slip through. You want
|
||||||
|
to do it just right such that your test can catch exactly the kind of bugs you
|
||||||
|
intend it to catch. gMock provides the necessary means for you to do it "just
|
||||||
|
right."
|
||||||
|
|
||||||
|
### General Syntax
|
||||||
|
|
||||||
|
In gMock we use the `EXPECT_CALL()` macro to set an expectation on a mock
|
||||||
|
method. The general syntax is:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
EXPECT_CALL(mock_object, method(matchers))
|
||||||
|
.Times(cardinality)
|
||||||
|
.WillOnce(action)
|
||||||
|
.WillRepeatedly(action);
|
||||||
|
```
|
||||||
|
|
||||||
|
The macro has two arguments: first the mock object, and then the method and its
|
||||||
|
arguments. Note that the two are separated by a comma (`,`), not a period (`.`).
|
||||||
|
(Why using a comma? The answer is that it was necessary for technical reasons.)
|
||||||
|
If the method is not overloaded, the macro can also be called without matchers:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
EXPECT_CALL(mock_object, non-overloaded-method)
|
||||||
|
.Times(cardinality)
|
||||||
|
.WillOnce(action)
|
||||||
|
.WillRepeatedly(action);
|
||||||
|
```
|
||||||
|
|
||||||
|
This syntax allows the test writer to specify "called with any arguments"
|
||||||
|
without explicitly specifying the number or types of arguments. To avoid
|
||||||
|
unintended ambiguity, this syntax may only be used for methods that are not
|
||||||
|
overloaded.
|
||||||
|
|
||||||
|
Either form of the macro can be followed by some optional *clauses* that provide
|
||||||
|
more information about the expectation. We'll discuss how each clause works in
|
||||||
|
the coming sections.
|
||||||
|
|
||||||
|
This syntax is designed to make an expectation read like English. For example,
|
||||||
|
you can probably guess that
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Return;
|
||||||
|
...
|
||||||
|
EXPECT_CALL(turtle, GetX())
|
||||||
|
.Times(5)
|
||||||
|
.WillOnce(Return(100))
|
||||||
|
.WillOnce(Return(150))
|
||||||
|
.WillRepeatedly(Return(200));
|
||||||
|
```
|
||||||
|
|
||||||
|
says that the `turtle` object's `GetX()` method will be called five times, it
|
||||||
|
will return 100 the first time, 150 the second time, and then 200 every time.
|
||||||
|
Some people like to call this style of syntax a Domain-Specific Language (DSL).
|
||||||
|
|
||||||
|
{: .callout .note}
|
||||||
|
**Note:** Why do we use a macro to do this? Well it serves two purposes: first
|
||||||
|
it makes expectations easily identifiable (either by `grep` or by a human
|
||||||
|
reader), and second it allows gMock to include the source file location of a
|
||||||
|
failed expectation in messages, making debugging easier.
|
||||||
|
|
||||||
|
### Matchers: What Arguments Do We Expect?
|
||||||
|
|
||||||
|
When a mock function takes arguments, we may specify what arguments we are
|
||||||
|
expecting, for example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Expects the turtle to move forward by 100 units.
|
||||||
|
EXPECT_CALL(turtle, Forward(100));
|
||||||
|
```
|
||||||
|
|
||||||
|
Oftentimes you do not want to be too specific. Remember that talk about tests
|
||||||
|
being too rigid? Over specification leads to brittle tests and obscures the
|
||||||
|
intent of tests. Therefore we encourage you to specify only what's necessary—no
|
||||||
|
more, no less. If you aren't interested in the value of an argument, write `_`
|
||||||
|
as the argument, which means "anything goes":
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::_;
|
||||||
|
...
|
||||||
|
// Expects that the turtle jumps to somewhere on the x=50 line.
|
||||||
|
EXPECT_CALL(turtle, GoTo(50, _));
|
||||||
|
```
|
||||||
|
|
||||||
|
`_` is an instance of what we call **matchers**. A matcher is like a predicate
|
||||||
|
and can test whether an argument is what we'd expect. You can use a matcher
|
||||||
|
inside `EXPECT_CALL()` wherever a function argument is expected. `_` is a
|
||||||
|
convenient way of saying "any value".
|
||||||
|
|
||||||
|
In the above examples, `100` and `50` are also matchers; implicitly, they are
|
||||||
|
the same as `Eq(100)` and `Eq(50)`, which specify that the argument must be
|
||||||
|
equal (using `operator==`) to the matcher argument. There are many
|
||||||
|
[built-in matchers](reference/matchers.md) for common types (as well as
|
||||||
|
[custom matchers](gmock_cook_book.md#NewMatchers)); for example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Ge;
|
||||||
|
...
|
||||||
|
// Expects the turtle moves forward by at least 100.
|
||||||
|
EXPECT_CALL(turtle, Forward(Ge(100)));
|
||||||
|
```
|
||||||
|
|
||||||
|
If you don't care about *any* arguments, rather than specify `_` for each of
|
||||||
|
them you may instead omit the parameter list:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Expects the turtle to move forward.
|
||||||
|
EXPECT_CALL(turtle, Forward);
|
||||||
|
// Expects the turtle to jump somewhere.
|
||||||
|
EXPECT_CALL(turtle, GoTo);
|
||||||
|
```
|
||||||
|
|
||||||
|
This works for all non-overloaded methods; if a method is overloaded, you need
|
||||||
|
to help gMock resolve which overload is expected by specifying the number of
|
||||||
|
arguments and possibly also the
|
||||||
|
[types of the arguments](gmock_cook_book.md#SelectOverload).
|
||||||
|
|
||||||
|
### Cardinalities: How Many Times Will It Be Called?
|
||||||
|
|
||||||
|
The first clause we can specify following an `EXPECT_CALL()` is `Times()`. We
|
||||||
|
call its argument a **cardinality** as it tells *how many times* the call should
|
||||||
|
occur. It allows us to repeat an expectation many times without actually writing
|
||||||
|
it as many times. More importantly, a cardinality can be "fuzzy", just like a
|
||||||
|
matcher can be. This allows a user to express the intent of a test exactly.
|
||||||
|
|
||||||
|
An interesting special case is when we say `Times(0)`. You may have guessed - it
|
||||||
|
means that the function shouldn't be called with the given arguments at all, and
|
||||||
|
gMock will report a googletest failure whenever the function is (wrongfully)
|
||||||
|
called.
|
||||||
|
|
||||||
|
We've seen `AtLeast(n)` as an example of fuzzy cardinalities earlier. For the
|
||||||
|
list of built-in cardinalities you can use, see
|
||||||
|
[here](gmock_cheat_sheet.md#CardinalityList).
|
||||||
|
|
||||||
|
The `Times()` clause can be omitted. **If you omit `Times()`, gMock will infer
|
||||||
|
the cardinality for you.** The rules are easy to remember:
|
||||||
|
|
||||||
|
* If **neither** `WillOnce()` **nor** `WillRepeatedly()` is in the
|
||||||
|
`EXPECT_CALL()`, the inferred cardinality is `Times(1)`.
|
||||||
|
* If there are *n* `WillOnce()`'s but **no** `WillRepeatedly()`, where *n* >=
|
||||||
|
1, the cardinality is `Times(n)`.
|
||||||
|
* If there are *n* `WillOnce()`'s and **one** `WillRepeatedly()`, where *n* >=
|
||||||
|
0, the cardinality is `Times(AtLeast(n))`.
|
||||||
|
|
||||||
|
**Quick quiz:** what do you think will happen if a function is expected to be
|
||||||
|
called twice but actually called four times?
|
||||||
|
|
||||||
|
### Actions: What Should It Do?
|
||||||
|
|
||||||
|
Remember that a mock object doesn't really have a working implementation? We as
|
||||||
|
users have to tell it what to do when a method is invoked. This is easy in
|
||||||
|
gMock.
|
||||||
|
|
||||||
|
First, if the return type of a mock function is a built-in type or a pointer,
|
||||||
|
the function has a **default action** (a `void` function will just return, a
|
||||||
|
`bool` function will return `false`, and other functions will return 0). In
|
||||||
|
addition, in C++ 11 and above, a mock function whose return type is
|
||||||
|
default-constructible (i.e. has a default constructor) has a default action of
|
||||||
|
returning a default-constructed value. If you don't say anything, this behavior
|
||||||
|
will be used.
|
||||||
|
|
||||||
|
Second, if a mock function doesn't have a default action, or the default action
|
||||||
|
doesn't suit you, you can specify the action to be taken each time the
|
||||||
|
expectation matches using a series of `WillOnce()` clauses followed by an
|
||||||
|
optional `WillRepeatedly()`. For example,
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Return;
|
||||||
|
...
|
||||||
|
EXPECT_CALL(turtle, GetX())
|
||||||
|
.WillOnce(Return(100))
|
||||||
|
.WillOnce(Return(200))
|
||||||
|
.WillOnce(Return(300));
|
||||||
|
```
|
||||||
|
|
||||||
|
says that `turtle.GetX()` will be called *exactly three times* (gMock inferred
|
||||||
|
this from how many `WillOnce()` clauses we've written, since we didn't
|
||||||
|
explicitly write `Times()`), and will return 100, 200, and 300 respectively.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Return;
|
||||||
|
...
|
||||||
|
EXPECT_CALL(turtle, GetY())
|
||||||
|
.WillOnce(Return(100))
|
||||||
|
.WillOnce(Return(200))
|
||||||
|
.WillRepeatedly(Return(300));
|
||||||
|
```
|
||||||
|
|
||||||
|
says that `turtle.GetY()` will be called *at least twice* (gMock knows this as
|
||||||
|
we've written two `WillOnce()` clauses and a `WillRepeatedly()` while having no
|
||||||
|
explicit `Times()`), will return 100 and 200 respectively the first two times,
|
||||||
|
and 300 from the third time on.
|
||||||
|
|
||||||
|
Of course, if you explicitly write a `Times()`, gMock will not try to infer the
|
||||||
|
cardinality itself. What if the number you specified is larger than there are
|
||||||
|
`WillOnce()` clauses? Well, after all `WillOnce()`s are used up, gMock will do
|
||||||
|
the *default* action for the function every time (unless, of course, you have a
|
||||||
|
`WillRepeatedly()`.).
|
||||||
|
|
||||||
|
What can we do inside `WillOnce()` besides `Return()`? You can return a
|
||||||
|
reference using `ReturnRef(`*`variable`*`)`, or invoke a pre-defined function,
|
||||||
|
among [others](gmock_cook_book.md#using-actions).
|
||||||
|
|
||||||
|
**Important note:** The `EXPECT_CALL()` statement evaluates the action clause
|
||||||
|
only once, even though the action may be performed many times. Therefore you
|
||||||
|
must be careful about side effects. The following may not do what you want:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Return;
|
||||||
|
...
|
||||||
|
int n = 100;
|
||||||
|
EXPECT_CALL(turtle, GetX())
|
||||||
|
.Times(4)
|
||||||
|
.WillRepeatedly(Return(n++));
|
||||||
|
```
|
||||||
|
|
||||||
|
Instead of returning 100, 101, 102, ..., consecutively, this mock function will
|
||||||
|
always return 100 as `n++` is only evaluated once. Similarly, `Return(new Foo)`
|
||||||
|
will create a new `Foo` object when the `EXPECT_CALL()` is executed, and will
|
||||||
|
return the same pointer every time. If you want the side effect to happen every
|
||||||
|
time, you need to define a custom action, which we'll teach in the
|
||||||
|
[cook book](gmock_cook_book.md).
|
||||||
|
|
||||||
|
Time for another quiz! What do you think the following means?
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Return;
|
||||||
|
...
|
||||||
|
EXPECT_CALL(turtle, GetY())
|
||||||
|
.Times(4)
|
||||||
|
.WillOnce(Return(100));
|
||||||
|
```
|
||||||
|
|
||||||
|
Obviously `turtle.GetY()` is expected to be called four times. But if you think
|
||||||
|
it will return 100 every time, think twice! Remember that one `WillOnce()`
|
||||||
|
clause will be consumed each time the function is invoked and the default action
|
||||||
|
will be taken afterwards. So the right answer is that `turtle.GetY()` will
|
||||||
|
return 100 the first time, but **return 0 from the second time on**, as
|
||||||
|
returning 0 is the default action for `int` functions.
|
||||||
|
|
||||||
|
### Using Multiple Expectations {#MultiExpectations}
|
||||||
|
|
||||||
|
So far we've only shown examples where you have a single expectation. More
|
||||||
|
realistically, you'll specify expectations on multiple mock methods which may be
|
||||||
|
from multiple mock objects.
|
||||||
|
|
||||||
|
By default, when a mock method is invoked, gMock will search the expectations in
|
||||||
|
the **reverse order** they are defined, and stop when an active expectation that
|
||||||
|
matches the arguments is found (you can think of it as "newer rules override
|
||||||
|
older ones."). If the matching expectation cannot take any more calls, you will
|
||||||
|
get an upper-bound-violated failure. Here's an example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::_;
|
||||||
|
...
|
||||||
|
EXPECT_CALL(turtle, Forward(_)); // #1
|
||||||
|
EXPECT_CALL(turtle, Forward(10)) // #2
|
||||||
|
.Times(2);
|
||||||
|
```
|
||||||
|
|
||||||
|
If `Forward(10)` is called three times in a row, the third time it will be an
|
||||||
|
error, as the last matching expectation (#2) has been saturated. If, however,
|
||||||
|
the third `Forward(10)` call is replaced by `Forward(20)`, then it would be OK,
|
||||||
|
as now #1 will be the matching expectation.
|
||||||
|
|
||||||
|
{: .callout .note}
|
||||||
|
**Note:** Why does gMock search for a match in the *reverse* order of the
|
||||||
|
expectations? The reason is that this allows a user to set up the default
|
||||||
|
expectations in a mock object's constructor or the test fixture's set-up phase
|
||||||
|
and then customize the mock by writing more specific expectations in the test
|
||||||
|
body. So, if you have two expectations on the same method, you want to put the
|
||||||
|
one with more specific matchers **after** the other, or the more specific rule
|
||||||
|
would be shadowed by the more general one that comes after it.
|
||||||
|
|
||||||
|
{: .callout .tip}
|
||||||
|
**Tip:** It is very common to start with a catch-all expectation for a method
|
||||||
|
and `Times(AnyNumber())` (omitting arguments, or with `_` for all arguments, if
|
||||||
|
overloaded). This makes any calls to the method expected. This is not necessary
|
||||||
|
for methods that are not mentioned at all (these are "uninteresting"), but is
|
||||||
|
useful for methods that have some expectations, but for which other calls are
|
||||||
|
ok. See
|
||||||
|
[Understanding Uninteresting vs Unexpected Calls](gmock_cook_book.md#uninteresting-vs-unexpected).
|
||||||
|
|
||||||
|
### Ordered vs Unordered Calls {#OrderedCalls}
|
||||||
|
|
||||||
|
By default, an expectation can match a call even though an earlier expectation
|
||||||
|
hasn't been satisfied. In other words, the calls don't have to occur in the
|
||||||
|
order the expectations are specified.
|
||||||
|
|
||||||
|
Sometimes, you may want all the expected calls to occur in a strict order. To
|
||||||
|
say this in gMock is easy:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::InSequence;
|
||||||
|
...
|
||||||
|
TEST(FooTest, DrawsLineSegment) {
|
||||||
|
...
|
||||||
|
{
|
||||||
|
InSequence seq;
|
||||||
|
|
||||||
|
EXPECT_CALL(turtle, PenDown());
|
||||||
|
EXPECT_CALL(turtle, Forward(100));
|
||||||
|
EXPECT_CALL(turtle, PenUp());
|
||||||
|
}
|
||||||
|
Foo();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
By creating an object of type `InSequence`, all expectations in its scope are
|
||||||
|
put into a *sequence* and have to occur *sequentially*. Since we are just
|
||||||
|
relying on the constructor and destructor of this object to do the actual work,
|
||||||
|
its name is really irrelevant.
|
||||||
|
|
||||||
|
In this example, we test that `Foo()` calls the three expected functions in the
|
||||||
|
order as written. If a call is made out-of-order, it will be an error.
|
||||||
|
|
||||||
|
(What if you care about the relative order of some of the calls, but not all of
|
||||||
|
them? Can you specify an arbitrary partial order? The answer is ... yes! The
|
||||||
|
details can be found [here](gmock_cook_book.md#OrderedCalls).)
|
||||||
|
|
||||||
|
### All Expectations Are Sticky (Unless Said Otherwise) {#StickyExpectations}
|
||||||
|
|
||||||
|
Now let's do a quick quiz to see how well you can use this mock stuff already.
|
||||||
|
How would you test that the turtle is asked to go to the origin *exactly twice*
|
||||||
|
(you want to ignore any other instructions it receives)?
|
||||||
|
|
||||||
|
After you've come up with your answer, take a look at ours and compare notes
|
||||||
|
(solve it yourself first - don't cheat!):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::_;
|
||||||
|
using ::testing::AnyNumber;
|
||||||
|
...
|
||||||
|
EXPECT_CALL(turtle, GoTo(_, _)) // #1
|
||||||
|
.Times(AnyNumber());
|
||||||
|
EXPECT_CALL(turtle, GoTo(0, 0)) // #2
|
||||||
|
.Times(2);
|
||||||
|
```
|
||||||
|
|
||||||
|
Suppose `turtle.GoTo(0, 0)` is called three times. In the third time, gMock will
|
||||||
|
see that the arguments match expectation #2 (remember that we always pick the
|
||||||
|
last matching expectation). Now, since we said that there should be only two
|
||||||
|
such calls, gMock will report an error immediately. This is basically what we've
|
||||||
|
told you in the [Using Multiple Expectations](#MultiExpectations) section above.
|
||||||
|
|
||||||
|
This example shows that **expectations in gMock are "sticky" by default**, in
|
||||||
|
the sense that they remain active even after we have reached their invocation
|
||||||
|
upper bounds. This is an important rule to remember, as it affects the meaning
|
||||||
|
of the spec, and is **different** to how it's done in many other mocking
|
||||||
|
frameworks (Why'd we do that? Because we think our rule makes the common cases
|
||||||
|
easier to express and understand.).
|
||||||
|
|
||||||
|
Simple? Let's see if you've really understood it: what does the following code
|
||||||
|
say?
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Return;
|
||||||
|
...
|
||||||
|
for (int i = n; i > 0; i--) {
|
||||||
|
EXPECT_CALL(turtle, GetX())
|
||||||
|
.WillOnce(Return(10*i));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you think it says that `turtle.GetX()` will be called `n` times and will
|
||||||
|
return 10, 20, 30, ..., consecutively, think twice! The problem is that, as we
|
||||||
|
said, expectations are sticky. So, the second time `turtle.GetX()` is called,
|
||||||
|
the last (latest) `EXPECT_CALL()` statement will match, and will immediately
|
||||||
|
lead to an "upper bound violated" error - this piece of code is not very useful!
|
||||||
|
|
||||||
|
One correct way of saying that `turtle.GetX()` will return 10, 20, 30, ..., is
|
||||||
|
to explicitly say that the expectations are *not* sticky. In other words, they
|
||||||
|
should *retire* as soon as they are saturated:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Return;
|
||||||
|
...
|
||||||
|
for (int i = n; i > 0; i--) {
|
||||||
|
EXPECT_CALL(turtle, GetX())
|
||||||
|
.WillOnce(Return(10*i))
|
||||||
|
.RetiresOnSaturation();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And, there's a better way to do it: in this case, we expect the calls to occur
|
||||||
|
in a specific order, and we line up the actions to match the order. Since the
|
||||||
|
order is important here, we should make it explicit using a sequence:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::InSequence;
|
||||||
|
using ::testing::Return;
|
||||||
|
...
|
||||||
|
{
|
||||||
|
InSequence s;
|
||||||
|
|
||||||
|
for (int i = 1; i <= n; i++) {
|
||||||
|
EXPECT_CALL(turtle, GetX())
|
||||||
|
.WillOnce(Return(10*i))
|
||||||
|
.RetiresOnSaturation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
By the way, the other situation where an expectation may *not* be sticky is when
|
||||||
|
it's in a sequence - as soon as another expectation that comes after it in the
|
||||||
|
sequence has been used, it automatically retires (and will never be used to
|
||||||
|
match any call).
|
||||||
|
|
||||||
|
### Uninteresting Calls
|
||||||
|
|
||||||
|
A mock object may have many methods, and not all of them are that interesting.
|
||||||
|
For example, in some tests we may not care about how many times `GetX()` and
|
||||||
|
`GetY()` get called.
|
||||||
|
|
||||||
|
In gMock, if you are not interested in a method, just don't say anything about
|
||||||
|
it. If a call to this method occurs, you'll see a warning in the test output,
|
||||||
|
but it won't be a failure. This is called "naggy" behavior; to change, see
|
||||||
|
[The Nice, the Strict, and the Naggy](gmock_cook_book.md#NiceStrictNaggy).
|
|
@ -0,0 +1,22 @@
|
||||||
|
# GoogleTest User's Guide
|
||||||
|
|
||||||
|
## Welcome to GoogleTest!
|
||||||
|
|
||||||
|
GoogleTest is Google's C++ testing and mocking framework. This user's guide has
|
||||||
|
the following contents:
|
||||||
|
|
||||||
|
* [GoogleTest Primer](primer.md) - Teaches you how to write simple tests using
|
||||||
|
GoogleTest. Read this first if you are new to GoogleTest.
|
||||||
|
* [GoogleTest Advanced](advanced.md) - Read this when you've finished the
|
||||||
|
Primer and want to utilize GoogleTest to its full potential.
|
||||||
|
* [GoogleTest Samples](samples.md) - Describes some GoogleTest samples.
|
||||||
|
* [GoogleTest FAQ](faq.md) - Have a question? Want some tips? Check here
|
||||||
|
first.
|
||||||
|
* [Mocking for Dummies](gmock_for_dummies.md) - Teaches you how to create mock
|
||||||
|
objects and use them in tests.
|
||||||
|
* [Mocking Cookbook](gmock_cook_book.md) - Includes tips and approaches to
|
||||||
|
common mocking use cases.
|
||||||
|
* [Mocking Cheat Sheet](gmock_cheat_sheet.md) - A handy reference for
|
||||||
|
matchers, actions, invariants, and more.
|
||||||
|
* [Mocking FAQ](gmock_faq.md) - Contains answers to some mocking-specific
|
||||||
|
questions.
|
|
@ -0,0 +1,144 @@
|
||||||
|
## Using GoogleTest from various build systems
|
||||||
|
|
||||||
|
GoogleTest comes with pkg-config files that can be used to determine all
|
||||||
|
necessary flags for compiling and linking to GoogleTest (and GoogleMock).
|
||||||
|
Pkg-config is a standardised plain-text format containing
|
||||||
|
|
||||||
|
* the includedir (-I) path
|
||||||
|
* necessary macro (-D) definitions
|
||||||
|
* further required flags (-pthread)
|
||||||
|
* the library (-L) path
|
||||||
|
* the library (-l) to link to
|
||||||
|
|
||||||
|
All current build systems support pkg-config in one way or another. For all
|
||||||
|
examples here we assume you want to compile the sample
|
||||||
|
`samples/sample3_unittest.cc`.
|
||||||
|
|
||||||
|
### CMake
|
||||||
|
|
||||||
|
Using `pkg-config` in CMake is fairly easy:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
find_package(PkgConfig)
|
||||||
|
pkg_search_module(GTEST REQUIRED gtest_main)
|
||||||
|
|
||||||
|
add_executable(testapp)
|
||||||
|
target_sources(testapp PRIVATE samples/sample3_unittest.cc)
|
||||||
|
target_link_libraries(testapp PRIVATE ${GTEST_LDFLAGS})
|
||||||
|
target_compile_options(testapp PRIVATE ${GTEST_CFLAGS})
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
add_test(first_and_only_test testapp)
|
||||||
|
```
|
||||||
|
|
||||||
|
It is generally recommended that you use `target_compile_options` + `_CFLAGS`
|
||||||
|
over `target_include_directories` + `_INCLUDE_DIRS` as the former includes not
|
||||||
|
just -I flags (GoogleTest might require a macro indicating to internal headers
|
||||||
|
that all libraries have been compiled with threading enabled. In addition,
|
||||||
|
GoogleTest might also require `-pthread` in the compiling step, and as such
|
||||||
|
splitting the pkg-config `Cflags` variable into include dirs and macros for
|
||||||
|
`target_compile_definitions()` might still miss this). The same recommendation
|
||||||
|
goes for using `_LDFLAGS` over the more commonplace `_LIBRARIES`, which happens
|
||||||
|
to discard `-L` flags and `-pthread`.
|
||||||
|
|
||||||
|
### Help! pkg-config can't find GoogleTest!
|
||||||
|
|
||||||
|
Let's say you have a `CMakeLists.txt` along the lines of the one in this
|
||||||
|
tutorial and you try to run `cmake`. It is very possible that you get a failure
|
||||||
|
along the lines of:
|
||||||
|
|
||||||
|
```
|
||||||
|
-- Checking for one of the modules 'gtest_main'
|
||||||
|
CMake Error at /usr/share/cmake/Modules/FindPkgConfig.cmake:640 (message):
|
||||||
|
None of the required 'gtest_main' found
|
||||||
|
```
|
||||||
|
|
||||||
|
These failures are common if you installed GoogleTest yourself and have not
|
||||||
|
sourced it from a distro or other package manager. If so, you need to tell
|
||||||
|
pkg-config where it can find the `.pc` files containing the information. Say you
|
||||||
|
installed GoogleTest to `/usr/local`, then it might be that the `.pc` files are
|
||||||
|
installed under `/usr/local/lib64/pkgconfig`. If you set
|
||||||
|
|
||||||
|
```
|
||||||
|
export PKG_CONFIG_PATH=/usr/local/lib64/pkgconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
pkg-config will also try to look in `PKG_CONFIG_PATH` to find `gtest_main.pc`.
|
||||||
|
|
||||||
|
### Using pkg-config in a cross-compilation setting
|
||||||
|
|
||||||
|
Pkg-config can be used in a cross-compilation setting too. To do this, let's
|
||||||
|
assume the final prefix of the cross-compiled installation will be `/usr`, and
|
||||||
|
your sysroot is `/home/MYUSER/sysroot`. Configure and install GTest using
|
||||||
|
|
||||||
|
```
|
||||||
|
mkdir build && cmake -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||||
|
```
|
||||||
|
|
||||||
|
Install into the sysroot using `DESTDIR`:
|
||||||
|
|
||||||
|
```
|
||||||
|
make -j install DESTDIR=/home/MYUSER/sysroot
|
||||||
|
```
|
||||||
|
|
||||||
|
Before we continue, it is recommended to **always** define the following two
|
||||||
|
variables for pkg-config in a cross-compilation setting:
|
||||||
|
|
||||||
|
```
|
||||||
|
export PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=yes
|
||||||
|
export PKG_CONFIG_ALLOW_SYSTEM_LIBS=yes
|
||||||
|
```
|
||||||
|
|
||||||
|
otherwise `pkg-config` will filter `-I` and `-L` flags against standard prefixes
|
||||||
|
such as `/usr` (see https://bugs.freedesktop.org/show_bug.cgi?id=28264#c3 for
|
||||||
|
reasons why this stripping needs to occur usually).
|
||||||
|
|
||||||
|
If you look at the generated pkg-config file, it will look something like
|
||||||
|
|
||||||
|
```
|
||||||
|
libdir=/usr/lib64
|
||||||
|
includedir=/usr/include
|
||||||
|
|
||||||
|
Name: gtest
|
||||||
|
Description: GoogleTest (without main() function)
|
||||||
|
Version: 1.11.0
|
||||||
|
URL: https://github.com/google/googletest
|
||||||
|
Libs: -L${libdir} -lgtest -lpthread
|
||||||
|
Cflags: -I${includedir} -DGTEST_HAS_PTHREAD=1 -lpthread
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice that the sysroot is not included in `libdir` and `includedir`! If you try
|
||||||
|
to run `pkg-config` with the correct
|
||||||
|
`PKG_CONFIG_LIBDIR=/home/MYUSER/sysroot/usr/lib64/pkgconfig` against this `.pc`
|
||||||
|
file, you will get
|
||||||
|
|
||||||
|
```
|
||||||
|
$ pkg-config --cflags gtest
|
||||||
|
-DGTEST_HAS_PTHREAD=1 -lpthread -I/usr/include
|
||||||
|
$ pkg-config --libs gtest
|
||||||
|
-L/usr/lib64 -lgtest -lpthread
|
||||||
|
```
|
||||||
|
|
||||||
|
which is obviously wrong and points to the `CBUILD` and not `CHOST` root. In
|
||||||
|
order to use this in a cross-compilation setting, we need to tell pkg-config to
|
||||||
|
inject the actual sysroot into `-I` and `-L` variables. Let us now tell
|
||||||
|
pkg-config about the actual sysroot
|
||||||
|
|
||||||
|
```
|
||||||
|
export PKG_CONFIG_DIR=
|
||||||
|
export PKG_CONFIG_SYSROOT_DIR=/home/MYUSER/sysroot
|
||||||
|
export PKG_CONFIG_LIBDIR=${PKG_CONFIG_SYSROOT_DIR}/usr/lib64/pkgconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
and running `pkg-config` again we get
|
||||||
|
|
||||||
|
```
|
||||||
|
$ pkg-config --cflags gtest
|
||||||
|
-DGTEST_HAS_PTHREAD=1 -lpthread -I/home/MYUSER/sysroot/usr/include
|
||||||
|
$ pkg-config --libs gtest
|
||||||
|
-L/home/MYUSER/sysroot/usr/lib64 -lgtest -lpthread
|
||||||
|
```
|
||||||
|
|
||||||
|
which contains the correct sysroot now. For a more comprehensive guide to also
|
||||||
|
including `${CHOST}` in build system calls, see the excellent tutorial by Diego
|
||||||
|
Elio Pettenò: <https://autotools.io/pkgconfig/cross-compiling.html>
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Supported Platforms
|
||||||
|
|
||||||
|
GoogleTest follows Google's
|
||||||
|
[Foundational C++ Support Policy](https://opensource.google/documentation/policies/cplusplus-support).
|
||||||
|
See
|
||||||
|
[this table](https://github.com/google/oss-policies-info/blob/main/foundational-cxx-support-matrix.md)
|
||||||
|
for a list of currently supported versions compilers, platforms, and build
|
||||||
|
tools.
|
|
@ -0,0 +1,483 @@
|
||||||
|
# GoogleTest Primer
|
||||||
|
|
||||||
|
## Introduction: Why GoogleTest?
|
||||||
|
|
||||||
|
*GoogleTest* helps you write better C++ tests.
|
||||||
|
|
||||||
|
GoogleTest is a testing framework developed by the Testing Technology team with
|
||||||
|
Google's specific requirements and constraints in mind. Whether you work on
|
||||||
|
Linux, Windows, or a Mac, if you write C++ code, GoogleTest can help you. And it
|
||||||
|
supports *any* kind of tests, not just unit tests.
|
||||||
|
|
||||||
|
So what makes a good test, and how does GoogleTest fit in? We believe:
|
||||||
|
|
||||||
|
1. Tests should be *independent* and *repeatable*. It's a pain to debug a test
|
||||||
|
that succeeds or fails as a result of other tests. GoogleTest isolates the
|
||||||
|
tests by running each of them on a different object. When a test fails,
|
||||||
|
GoogleTest allows you to run it in isolation for quick debugging.
|
||||||
|
2. Tests should be well *organized* and reflect the structure of the tested
|
||||||
|
code. GoogleTest groups related tests into test suites that can share data
|
||||||
|
and subroutines. This common pattern is easy to recognize and makes tests
|
||||||
|
easy to maintain. Such consistency is especially helpful when people switch
|
||||||
|
projects and start to work on a new code base.
|
||||||
|
3. Tests should be *portable* and *reusable*. Google has a lot of code that is
|
||||||
|
platform-neutral; its tests should also be platform-neutral. GoogleTest
|
||||||
|
works on different OSes, with different compilers, with or without
|
||||||
|
exceptions, so GoogleTest tests can work with a variety of configurations.
|
||||||
|
4. When tests fail, they should provide as much *information* about the problem
|
||||||
|
as possible. GoogleTest doesn't stop at the first test failure. Instead, it
|
||||||
|
only stops the current test and continues with the next. You can also set up
|
||||||
|
tests that report non-fatal failures after which the current test continues.
|
||||||
|
Thus, you can detect and fix multiple bugs in a single run-edit-compile
|
||||||
|
cycle.
|
||||||
|
5. The testing framework should liberate test writers from housekeeping chores
|
||||||
|
and let them focus on the test *content*. GoogleTest automatically keeps
|
||||||
|
track of all tests defined, and doesn't require the user to enumerate them
|
||||||
|
in order to run them.
|
||||||
|
6. Tests should be *fast*. With GoogleTest, you can reuse shared resources
|
||||||
|
across tests and pay for the set-up/tear-down only once, without making
|
||||||
|
tests depend on each other.
|
||||||
|
|
||||||
|
Since GoogleTest is based on the popular xUnit architecture, you'll feel right
|
||||||
|
at home if you've used JUnit or PyUnit before. If not, it will take you about 10
|
||||||
|
minutes to learn the basics and get started. So let's go!
|
||||||
|
|
||||||
|
## Beware of the Nomenclature
|
||||||
|
|
||||||
|
{: .callout .note}
|
||||||
|
*Note:* There might be some confusion arising from different definitions of the
|
||||||
|
terms *Test*, *Test Case* and *Test Suite*, so beware of misunderstanding these.
|
||||||
|
|
||||||
|
Historically, GoogleTest started to use the term *Test Case* for grouping
|
||||||
|
related tests, whereas current publications, including International Software
|
||||||
|
Testing Qualifications Board ([ISTQB](http://www.istqb.org/)) materials and
|
||||||
|
various textbooks on software quality, use the term
|
||||||
|
*[Test Suite][istqb test suite]* for this.
|
||||||
|
|
||||||
|
The related term *Test*, as it is used in GoogleTest, corresponds to the term
|
||||||
|
*[Test Case][istqb test case]* of ISTQB and others.
|
||||||
|
|
||||||
|
The term *Test* is commonly of broad enough sense, including ISTQB's definition
|
||||||
|
of *Test Case*, so it's not much of a problem here. But the term *Test Case* as
|
||||||
|
was used in Google Test is of contradictory sense and thus confusing.
|
||||||
|
|
||||||
|
GoogleTest recently started replacing the term *Test Case* with *Test Suite*.
|
||||||
|
The preferred API is *TestSuite*. The older TestCase API is being slowly
|
||||||
|
deprecated and refactored away.
|
||||||
|
|
||||||
|
So please be aware of the different definitions of the terms:
|
||||||
|
|
||||||
|
|
||||||
|
Meaning | GoogleTest Term | [ISTQB](http://www.istqb.org/) Term
|
||||||
|
:----------------------------------------------------------------------------------- | :---------------------- | :----------------------------------
|
||||||
|
Exercise a particular program path with specific input values and verify the results | [TEST()](#simple-tests) | [Test Case][istqb test case]
|
||||||
|
|
||||||
|
|
||||||
|
[istqb test case]: http://glossary.istqb.org/en/search/test%20case
|
||||||
|
[istqb test suite]: http://glossary.istqb.org/en/search/test%20suite
|
||||||
|
|
||||||
|
## Basic Concepts
|
||||||
|
|
||||||
|
When using GoogleTest, you start by writing *assertions*, which are statements
|
||||||
|
that check whether a condition is true. An assertion's result can be *success*,
|
||||||
|
*nonfatal failure*, or *fatal failure*. If a fatal failure occurs, it aborts the
|
||||||
|
current function; otherwise the program continues normally.
|
||||||
|
|
||||||
|
*Tests* use assertions to verify the tested code's behavior. If a test crashes
|
||||||
|
or has a failed assertion, then it *fails*; otherwise it *succeeds*.
|
||||||
|
|
||||||
|
A *test suite* contains one or many tests. You should group your tests into test
|
||||||
|
suites that reflect the structure of the tested code. When multiple tests in a
|
||||||
|
test suite need to share common objects and subroutines, you can put them into a
|
||||||
|
*test fixture* class.
|
||||||
|
|
||||||
|
A *test program* can contain multiple test suites.
|
||||||
|
|
||||||
|
We'll now explain how to write a test program, starting at the individual
|
||||||
|
assertion level and building up to tests and test suites.
|
||||||
|
|
||||||
|
## Assertions
|
||||||
|
|
||||||
|
GoogleTest assertions are macros that resemble function calls. You test a class
|
||||||
|
or function by making assertions about its behavior. When an assertion fails,
|
||||||
|
GoogleTest prints the assertion's source file and line number location, along
|
||||||
|
with a failure message. You may also supply a custom failure message which will
|
||||||
|
be appended to GoogleTest's message.
|
||||||
|
|
||||||
|
The assertions come in pairs that test the same thing but have different effects
|
||||||
|
on the current function. `ASSERT_*` versions generate fatal failures when they
|
||||||
|
fail, and **abort the current function**. `EXPECT_*` versions generate nonfatal
|
||||||
|
failures, which don't abort the current function. Usually `EXPECT_*` are
|
||||||
|
preferred, as they allow more than one failure to be reported in a test.
|
||||||
|
However, you should use `ASSERT_*` if it doesn't make sense to continue when the
|
||||||
|
assertion in question fails.
|
||||||
|
|
||||||
|
Since a failed `ASSERT_*` returns from the current function immediately,
|
||||||
|
possibly skipping clean-up code that comes after it, it may cause a space leak.
|
||||||
|
Depending on the nature of the leak, it may or may not be worth fixing - so keep
|
||||||
|
this in mind if you get a heap checker error in addition to assertion errors.
|
||||||
|
|
||||||
|
To provide a custom failure message, simply stream it into the macro using the
|
||||||
|
`<<` operator or a sequence of such operators. See the following example, using
|
||||||
|
the [`ASSERT_EQ` and `EXPECT_EQ`](reference/assertions.md#EXPECT_EQ) macros to
|
||||||
|
verify value equality:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length";
|
||||||
|
|
||||||
|
for (int i = 0; i < x.size(); ++i) {
|
||||||
|
EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Anything that can be streamed to an `ostream` can be streamed to an assertion
|
||||||
|
macro--in particular, C strings and `string` objects. If a wide string
|
||||||
|
(`wchar_t*`, `TCHAR*` in `UNICODE` mode on Windows, or `std::wstring`) is
|
||||||
|
streamed to an assertion, it will be translated to UTF-8 when printed.
|
||||||
|
|
||||||
|
GoogleTest provides a collection of assertions for verifying the behavior of
|
||||||
|
your code in various ways. You can check Boolean conditions, compare values
|
||||||
|
based on relational operators, verify string values, floating-point values, and
|
||||||
|
much more. There are even assertions that enable you to verify more complex
|
||||||
|
states by providing custom predicates. For the complete list of assertions
|
||||||
|
provided by GoogleTest, see the [Assertions Reference](reference/assertions.md).
|
||||||
|
|
||||||
|
## Simple Tests
|
||||||
|
|
||||||
|
To create a test:
|
||||||
|
|
||||||
|
1. Use the `TEST()` macro to define and name a test function. These are
|
||||||
|
ordinary C++ functions that don't return a value.
|
||||||
|
2. In this function, along with any valid C++ statements you want to include,
|
||||||
|
use the various GoogleTest assertions to check values.
|
||||||
|
3. The test's result is determined by the assertions; if any assertion in the
|
||||||
|
test fails (either fatally or non-fatally), or if the test crashes, the
|
||||||
|
entire test fails. Otherwise, it succeeds.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
TEST(TestSuiteName, TestName) {
|
||||||
|
... test body ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`TEST()` arguments go from general to specific. The *first* argument is the name
|
||||||
|
of the test suite, and the *second* argument is the test's name within the test
|
||||||
|
suite. Both names must be valid C++ identifiers, and they should not contain any
|
||||||
|
underscores (`_`). A test's *full name* consists of its containing test suite
|
||||||
|
and its individual name. Tests from different test suites can have the same
|
||||||
|
individual name.
|
||||||
|
|
||||||
|
For example, let's take a simple integer function:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
int Factorial(int n); // Returns the factorial of n
|
||||||
|
```
|
||||||
|
|
||||||
|
A test suite for this function might look like:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
// Tests factorial of 0.
|
||||||
|
TEST(FactorialTest, HandlesZeroInput) {
|
||||||
|
EXPECT_EQ(Factorial(0), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests factorial of positive numbers.
|
||||||
|
TEST(FactorialTest, HandlesPositiveInput) {
|
||||||
|
EXPECT_EQ(Factorial(1), 1);
|
||||||
|
EXPECT_EQ(Factorial(2), 2);
|
||||||
|
EXPECT_EQ(Factorial(3), 6);
|
||||||
|
EXPECT_EQ(Factorial(8), 40320);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
GoogleTest groups the test results by test suites, so logically related tests
|
||||||
|
should be in the same test suite; in other words, the first argument to their
|
||||||
|
`TEST()` should be the same. In the above example, we have two tests,
|
||||||
|
`HandlesZeroInput` and `HandlesPositiveInput`, that belong to the same test
|
||||||
|
suite `FactorialTest`.
|
||||||
|
|
||||||
|
When naming your test suites and tests, you should follow the same convention as
|
||||||
|
for
|
||||||
|
[naming functions and classes](https://google.github.io/styleguide/cppguide.html#Function_Names).
|
||||||
|
|
||||||
|
**Availability**: Linux, Windows, Mac.
|
||||||
|
|
||||||
|
## Test Fixtures: Using the Same Data Configuration for Multiple Tests {#same-data-multiple-tests}
|
||||||
|
|
||||||
|
If you find yourself writing two or more tests that operate on similar data, you
|
||||||
|
can use a *test fixture*. This allows you to reuse the same configuration of
|
||||||
|
objects for several different tests.
|
||||||
|
|
||||||
|
To create a fixture:
|
||||||
|
|
||||||
|
1. Derive a class from `::testing::Test` . Start its body with `protected:`, as
|
||||||
|
we'll want to access fixture members from sub-classes.
|
||||||
|
2. Inside the class, declare any objects you plan to use.
|
||||||
|
3. If necessary, write a default constructor or `SetUp()` function to prepare
|
||||||
|
the objects for each test. A common mistake is to spell `SetUp()` as
|
||||||
|
**`Setup()`** with a small `u` - Use `override` in C++11 to make sure you
|
||||||
|
spelled it correctly.
|
||||||
|
4. If necessary, write a destructor or `TearDown()` function to release any
|
||||||
|
resources you allocated in `SetUp()` . To learn when you should use the
|
||||||
|
constructor/destructor and when you should use `SetUp()/TearDown()`, read
|
||||||
|
the [FAQ](faq.md#CtorVsSetUp).
|
||||||
|
5. If needed, define subroutines for your tests to share.
|
||||||
|
|
||||||
|
When using a fixture, use `TEST_F()` instead of `TEST()` as it allows you to
|
||||||
|
access objects and subroutines in the test fixture:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
TEST_F(TestFixtureClassName, TestName) {
|
||||||
|
... test body ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Unlike `TEST()`, in `TEST_F()` the first argument must be the name of the test
|
||||||
|
fixture class. (`_F` stands for "Fixture"). No test suite name is specified for
|
||||||
|
this macro.
|
||||||
|
|
||||||
|
Unfortunately, the C++ macro system does not allow us to create a single macro
|
||||||
|
that can handle both types of tests. Using the wrong macro causes a compiler
|
||||||
|
error.
|
||||||
|
|
||||||
|
Also, you must first define a test fixture class before using it in a
|
||||||
|
`TEST_F()`, or you'll get the compiler error "`virtual outside class
|
||||||
|
declaration`".
|
||||||
|
|
||||||
|
For each test defined with `TEST_F()`, GoogleTest will create a *fresh* test
|
||||||
|
fixture at runtime, immediately initialize it via `SetUp()`, run the test, clean
|
||||||
|
up by calling `TearDown()`, and then delete the test fixture. Note that
|
||||||
|
different tests in the same test suite have different test fixture objects, and
|
||||||
|
GoogleTest always deletes a test fixture before it creates the next one.
|
||||||
|
GoogleTest does **not** reuse the same test fixture for multiple tests. Any
|
||||||
|
changes one test makes to the fixture do not affect other tests.
|
||||||
|
|
||||||
|
As an example, let's write tests for a FIFO queue class named `Queue`, which has
|
||||||
|
the following interface:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
template <typename E> // E is the element type.
|
||||||
|
class Queue {
|
||||||
|
public:
|
||||||
|
Queue();
|
||||||
|
void Enqueue(const E& element);
|
||||||
|
E* Dequeue(); // Returns NULL if the queue is empty.
|
||||||
|
size_t size() const;
|
||||||
|
...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
First, define a fixture class. By convention, you should give it the name
|
||||||
|
`FooTest` where `Foo` is the class being tested.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
class QueueTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
// q0_ remains empty
|
||||||
|
q1_.Enqueue(1);
|
||||||
|
q2_.Enqueue(2);
|
||||||
|
q2_.Enqueue(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// void TearDown() override {}
|
||||||
|
|
||||||
|
Queue<int> q0_;
|
||||||
|
Queue<int> q1_;
|
||||||
|
Queue<int> q2_;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case, `TearDown()` is not needed since we don't have to clean up after
|
||||||
|
each test, other than what's already done by the destructor.
|
||||||
|
|
||||||
|
Now we'll write tests using `TEST_F()` and this fixture.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
TEST_F(QueueTest, IsEmptyInitially) {
|
||||||
|
EXPECT_EQ(q0_.size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(QueueTest, DequeueWorks) {
|
||||||
|
int* n = q0_.Dequeue();
|
||||||
|
EXPECT_EQ(n, nullptr);
|
||||||
|
|
||||||
|
n = q1_.Dequeue();
|
||||||
|
ASSERT_NE(n, nullptr);
|
||||||
|
EXPECT_EQ(*n, 1);
|
||||||
|
EXPECT_EQ(q1_.size(), 0);
|
||||||
|
delete n;
|
||||||
|
|
||||||
|
n = q2_.Dequeue();
|
||||||
|
ASSERT_NE(n, nullptr);
|
||||||
|
EXPECT_EQ(*n, 2);
|
||||||
|
EXPECT_EQ(q2_.size(), 1);
|
||||||
|
delete n;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The above uses both `ASSERT_*` and `EXPECT_*` assertions. The rule of thumb is
|
||||||
|
to use `EXPECT_*` when you want the test to continue to reveal more errors after
|
||||||
|
the assertion failure, and use `ASSERT_*` when continuing after failure doesn't
|
||||||
|
make sense. For example, the second assertion in the `Dequeue` test is
|
||||||
|
`ASSERT_NE(n, nullptr)`, as we need to dereference the pointer `n` later, which
|
||||||
|
would lead to a segfault when `n` is `NULL`.
|
||||||
|
|
||||||
|
When these tests run, the following happens:
|
||||||
|
|
||||||
|
1. GoogleTest constructs a `QueueTest` object (let's call it `t1`).
|
||||||
|
2. `t1.SetUp()` initializes `t1`.
|
||||||
|
3. The first test (`IsEmptyInitially`) runs on `t1`.
|
||||||
|
4. `t1.TearDown()` cleans up after the test finishes.
|
||||||
|
5. `t1` is destructed.
|
||||||
|
6. The above steps are repeated on another `QueueTest` object, this time
|
||||||
|
running the `DequeueWorks` test.
|
||||||
|
|
||||||
|
**Availability**: Linux, Windows, Mac.
|
||||||
|
|
||||||
|
## Invoking the Tests
|
||||||
|
|
||||||
|
`TEST()` and `TEST_F()` implicitly register their tests with GoogleTest. So,
|
||||||
|
unlike with many other C++ testing frameworks, you don't have to re-list all
|
||||||
|
your defined tests in order to run them.
|
||||||
|
|
||||||
|
After defining your tests, you can run them with `RUN_ALL_TESTS()`, which
|
||||||
|
returns `0` if all the tests are successful, or `1` otherwise. Note that
|
||||||
|
`RUN_ALL_TESTS()` runs *all tests* in your link unit--they can be from different
|
||||||
|
test suites, or even different source files.
|
||||||
|
|
||||||
|
When invoked, the `RUN_ALL_TESTS()` macro:
|
||||||
|
|
||||||
|
* Saves the state of all GoogleTest flags.
|
||||||
|
|
||||||
|
* Creates a test fixture object for the first test.
|
||||||
|
|
||||||
|
* Initializes it via `SetUp()`.
|
||||||
|
|
||||||
|
* Runs the test on the fixture object.
|
||||||
|
|
||||||
|
* Cleans up the fixture via `TearDown()`.
|
||||||
|
|
||||||
|
* Deletes the fixture.
|
||||||
|
|
||||||
|
* Restores the state of all GoogleTest flags.
|
||||||
|
|
||||||
|
* Repeats the above steps for the next test, until all tests have run.
|
||||||
|
|
||||||
|
If a fatal failure happens the subsequent steps will be skipped.
|
||||||
|
|
||||||
|
{: .callout .important}
|
||||||
|
> IMPORTANT: You must **not** ignore the return value of `RUN_ALL_TESTS()`, or
|
||||||
|
> you will get a compiler error. The rationale for this design is that the
|
||||||
|
> automated testing service determines whether a test has passed based on its
|
||||||
|
> exit code, not on its stdout/stderr output; thus your `main()` function must
|
||||||
|
> return the value of `RUN_ALL_TESTS()`.
|
||||||
|
>
|
||||||
|
> Also, you should call `RUN_ALL_TESTS()` only **once**. Calling it more than
|
||||||
|
> once conflicts with some advanced GoogleTest features (e.g., thread-safe
|
||||||
|
> [death tests](advanced.md#death-tests)) and thus is not supported.
|
||||||
|
|
||||||
|
**Availability**: Linux, Windows, Mac.
|
||||||
|
|
||||||
|
## Writing the main() Function
|
||||||
|
|
||||||
|
Most users should *not* need to write their own `main` function and instead link
|
||||||
|
with `gtest_main` (as opposed to with `gtest`), which defines a suitable entry
|
||||||
|
point. See the end of this section for details. The remainder of this section
|
||||||
|
should only apply when you need to do something custom before the tests run that
|
||||||
|
cannot be expressed within the framework of fixtures and test suites.
|
||||||
|
|
||||||
|
If you write your own `main` function, it should return the value of
|
||||||
|
`RUN_ALL_TESTS()`.
|
||||||
|
|
||||||
|
You can start from this boilerplate:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
#include "this/package/foo.h"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
namespace my {
|
||||||
|
namespace project {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// The fixture for testing class Foo.
|
||||||
|
class FooTest : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
// You can remove any or all of the following functions if their bodies would
|
||||||
|
// be empty.
|
||||||
|
|
||||||
|
FooTest() {
|
||||||
|
// You can do set-up work for each test here.
|
||||||
|
}
|
||||||
|
|
||||||
|
~FooTest() override {
|
||||||
|
// You can do clean-up work that doesn't throw exceptions here.
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the constructor and destructor are not enough for setting up
|
||||||
|
// and cleaning up each test, you can define the following methods:
|
||||||
|
|
||||||
|
void SetUp() override {
|
||||||
|
// Code here will be called immediately after the constructor (right
|
||||||
|
// before each test).
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
// Code here will be called immediately after each test (right
|
||||||
|
// before the destructor).
|
||||||
|
}
|
||||||
|
|
||||||
|
// Class members declared here can be used by all tests in the test suite
|
||||||
|
// for Foo.
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tests that the Foo::Bar() method does Abc.
|
||||||
|
TEST_F(FooTest, MethodBarDoesAbc) {
|
||||||
|
const std::string input_filepath = "this/package/testdata/myinputfile.dat";
|
||||||
|
const std::string output_filepath = "this/package/testdata/myoutputfile.dat";
|
||||||
|
Foo f;
|
||||||
|
EXPECT_EQ(f.Bar(input_filepath, output_filepath), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that Foo does Xyz.
|
||||||
|
TEST_F(FooTest, DoesXyz) {
|
||||||
|
// Exercises the Xyz feature of Foo.
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace project
|
||||||
|
} // namespace my
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `::testing::InitGoogleTest()` function parses the command line for
|
||||||
|
GoogleTest flags, and removes all recognized flags. This allows the user to
|
||||||
|
control a test program's behavior via various flags, which we'll cover in the
|
||||||
|
[AdvancedGuide](advanced.md). You **must** call this function before calling
|
||||||
|
`RUN_ALL_TESTS()`, or the flags won't be properly initialized.
|
||||||
|
|
||||||
|
On Windows, `InitGoogleTest()` also works with wide strings, so it can be used
|
||||||
|
in programs compiled in `UNICODE` mode as well.
|
||||||
|
|
||||||
|
But maybe you think that writing all those `main` functions is too much work? We
|
||||||
|
agree with you completely, and that's why Google Test provides a basic
|
||||||
|
implementation of main(). If it fits your needs, then just link your test with
|
||||||
|
the `gtest_main` library and you are good to go.
|
||||||
|
|
||||||
|
{: .callout .note}
|
||||||
|
NOTE: `ParseGUnitFlags()` is deprecated in favor of `InitGoogleTest()`.
|
||||||
|
|
||||||
|
## Known Limitations
|
||||||
|
|
||||||
|
* Google Test is designed to be thread-safe. The implementation is thread-safe
|
||||||
|
on systems where the `pthreads` library is available. It is currently
|
||||||
|
*unsafe* to use Google Test assertions from two threads concurrently on
|
||||||
|
other systems (e.g. Windows). In most tests this is not an issue as usually
|
||||||
|
the assertions are done in the main thread. If you want to help, you can
|
||||||
|
volunteer to implement the necessary synchronization primitives in
|
||||||
|
`gtest-port.h` for your platform.
|
|
@ -0,0 +1,153 @@
|
||||||
|
# Quickstart: Building with Bazel
|
||||||
|
|
||||||
|
This tutorial aims to get you up and running with GoogleTest using the Bazel
|
||||||
|
build system. If you're using GoogleTest for the first time or need a refresher,
|
||||||
|
we recommend this tutorial as a starting point.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
To complete this tutorial, you'll need:
|
||||||
|
|
||||||
|
* A compatible operating system (e.g. Linux, macOS, Windows).
|
||||||
|
* A compatible C++ compiler that supports at least C++14.
|
||||||
|
* [Bazel](https://bazel.build/), the preferred build system used by the
|
||||||
|
GoogleTest team.
|
||||||
|
|
||||||
|
See [Supported Platforms](platforms.md) for more information about platforms
|
||||||
|
compatible with GoogleTest.
|
||||||
|
|
||||||
|
If you don't already have Bazel installed, see the
|
||||||
|
[Bazel installation guide](https://bazel.build/install).
|
||||||
|
|
||||||
|
{: .callout .note} Note: The terminal commands in this tutorial show a Unix
|
||||||
|
shell prompt, but the commands work on the Windows command line as well.
|
||||||
|
|
||||||
|
## Set up a Bazel workspace
|
||||||
|
|
||||||
|
A
|
||||||
|
[Bazel workspace](https://docs.bazel.build/versions/main/build-ref.html#workspace)
|
||||||
|
is a directory on your filesystem that you use to manage source files for the
|
||||||
|
software you want to build. Each workspace directory has a text file named
|
||||||
|
`WORKSPACE` which may be empty, or may contain references to external
|
||||||
|
dependencies required to build the outputs.
|
||||||
|
|
||||||
|
First, create a directory for your workspace:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ mkdir my_workspace && cd my_workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, you’ll create the `WORKSPACE` file to specify dependencies. A common and
|
||||||
|
recommended way to depend on GoogleTest is to use a
|
||||||
|
[Bazel external dependency](https://docs.bazel.build/versions/main/external.html)
|
||||||
|
via the
|
||||||
|
[`http_archive` rule](https://docs.bazel.build/versions/main/repo/http.html#http_archive).
|
||||||
|
To do this, in the root directory of your workspace (`my_workspace/`), create a
|
||||||
|
file named `WORKSPACE` with the following contents:
|
||||||
|
|
||||||
|
```
|
||||||
|
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||||
|
|
||||||
|
http_archive(
|
||||||
|
name = "com_google_googletest",
|
||||||
|
urls = ["https://github.com/google/googletest/archive/5ab508a01f9eb089207ee87fd547d290da39d015.zip"],
|
||||||
|
strip_prefix = "googletest-5ab508a01f9eb089207ee87fd547d290da39d015",
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
The above configuration declares a dependency on GoogleTest which is downloaded
|
||||||
|
as a ZIP archive from GitHub. In the above example,
|
||||||
|
`5ab508a01f9eb089207ee87fd547d290da39d015` is the Git commit hash of the
|
||||||
|
GoogleTest version to use; we recommend updating the hash often to point to the
|
||||||
|
latest version. Use a recent hash on the `main` branch.
|
||||||
|
|
||||||
|
Now you're ready to build C++ code that uses GoogleTest.
|
||||||
|
|
||||||
|
## Create and run a binary
|
||||||
|
|
||||||
|
With your Bazel workspace set up, you can now use GoogleTest code within your
|
||||||
|
own project.
|
||||||
|
|
||||||
|
As an example, create a file named `hello_test.cc` in your `my_workspace`
|
||||||
|
directory with the following contents:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
// Demonstrate some basic assertions.
|
||||||
|
TEST(HelloTest, BasicAssertions) {
|
||||||
|
// Expect two strings not to be equal.
|
||||||
|
EXPECT_STRNE("hello", "world");
|
||||||
|
// Expect equality.
|
||||||
|
EXPECT_EQ(7 * 6, 42);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
GoogleTest provides [assertions](primer.md#assertions) that you use to test the
|
||||||
|
behavior of your code. The above sample includes the main GoogleTest header file
|
||||||
|
and demonstrates some basic assertions.
|
||||||
|
|
||||||
|
To build the code, create a file named `BUILD` in the same directory with the
|
||||||
|
following contents:
|
||||||
|
|
||||||
|
```
|
||||||
|
cc_test(
|
||||||
|
name = "hello_test",
|
||||||
|
size = "small",
|
||||||
|
srcs = ["hello_test.cc"],
|
||||||
|
deps = ["@com_google_googletest//:gtest_main"],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
This `cc_test` rule declares the C++ test binary you want to build, and links to
|
||||||
|
GoogleTest (`//:gtest_main`) using the prefix you specified in the `WORKSPACE`
|
||||||
|
file (`@com_google_googletest`). For more information about Bazel `BUILD` files,
|
||||||
|
see the
|
||||||
|
[Bazel C++ Tutorial](https://docs.bazel.build/versions/main/tutorial/cpp.html).
|
||||||
|
|
||||||
|
{: .callout .note}
|
||||||
|
NOTE: In the example below, we assume Clang or GCC and set `--cxxopt=-std=c++14`
|
||||||
|
to ensure that GoogleTest is compiled as C++14 instead of the compiler's default
|
||||||
|
setting (which could be C++11). For MSVC, the equivalent would be
|
||||||
|
`--cxxopt=/std:c++14`. See [Supported Platforms](platforms.md) for more details
|
||||||
|
on supported language versions.
|
||||||
|
|
||||||
|
Now you can build and run your test:
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<strong>my_workspace$ bazel test --cxxopt=-std=c++14 --test_output=all //:hello_test</strong>
|
||||||
|
INFO: Analyzed target //:hello_test (26 packages loaded, 362 targets configured).
|
||||||
|
INFO: Found 1 test target...
|
||||||
|
INFO: From Testing //:hello_test:
|
||||||
|
==================== Test output for //:hello_test:
|
||||||
|
Running main() from gmock_main.cc
|
||||||
|
[==========] Running 1 test from 1 test suite.
|
||||||
|
[----------] Global test environment set-up.
|
||||||
|
[----------] 1 test from HelloTest
|
||||||
|
[ RUN ] HelloTest.BasicAssertions
|
||||||
|
[ OK ] HelloTest.BasicAssertions (0 ms)
|
||||||
|
[----------] 1 test from HelloTest (0 ms total)
|
||||||
|
|
||||||
|
[----------] Global test environment tear-down
|
||||||
|
[==========] 1 test from 1 test suite ran. (0 ms total)
|
||||||
|
[ PASSED ] 1 test.
|
||||||
|
================================================================================
|
||||||
|
Target //:hello_test up-to-date:
|
||||||
|
bazel-bin/hello_test
|
||||||
|
INFO: Elapsed time: 4.190s, Critical Path: 3.05s
|
||||||
|
INFO: 27 processes: 8 internal, 19 linux-sandbox.
|
||||||
|
INFO: Build completed successfully, 27 total actions
|
||||||
|
//:hello_test PASSED in 0.1s
|
||||||
|
|
||||||
|
INFO: Build completed successfully, 27 total actions
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
Congratulations! You've successfully built and run a test binary using
|
||||||
|
GoogleTest.
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
|
||||||
|
* [Check out the Primer](primer.md) to start learning how to write simple
|
||||||
|
tests.
|
||||||
|
* [See the code samples](samples.md) for more examples showing how to use a
|
||||||
|
variety of GoogleTest features.
|
|
@ -0,0 +1,157 @@
|
||||||
|
# Quickstart: Building with CMake
|
||||||
|
|
||||||
|
This tutorial aims to get you up and running with GoogleTest using CMake. If
|
||||||
|
you're using GoogleTest for the first time or need a refresher, we recommend
|
||||||
|
this tutorial as a starting point. If your project uses Bazel, see the
|
||||||
|
[Quickstart for Bazel](quickstart-bazel.md) instead.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
To complete this tutorial, you'll need:
|
||||||
|
|
||||||
|
* A compatible operating system (e.g. Linux, macOS, Windows).
|
||||||
|
* A compatible C++ compiler that supports at least C++14.
|
||||||
|
* [CMake](https://cmake.org/) and a compatible build tool for building the
|
||||||
|
project.
|
||||||
|
* Compatible build tools include
|
||||||
|
[Make](https://www.gnu.org/software/make/),
|
||||||
|
[Ninja](https://ninja-build.org/), and others - see
|
||||||
|
[CMake Generators](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html)
|
||||||
|
for more information.
|
||||||
|
|
||||||
|
See [Supported Platforms](platforms.md) for more information about platforms
|
||||||
|
compatible with GoogleTest.
|
||||||
|
|
||||||
|
If you don't already have CMake installed, see the
|
||||||
|
[CMake installation guide](https://cmake.org/install).
|
||||||
|
|
||||||
|
{: .callout .note}
|
||||||
|
Note: The terminal commands in this tutorial show a Unix shell prompt, but the
|
||||||
|
commands work on the Windows command line as well.
|
||||||
|
|
||||||
|
## Set up a project
|
||||||
|
|
||||||
|
CMake uses a file named `CMakeLists.txt` to configure the build system for a
|
||||||
|
project. You'll use this file to set up your project and declare a dependency on
|
||||||
|
GoogleTest.
|
||||||
|
|
||||||
|
First, create a directory for your project:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ mkdir my_project && cd my_project
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, you'll create the `CMakeLists.txt` file and declare a dependency on
|
||||||
|
GoogleTest. There are many ways to express dependencies in the CMake ecosystem;
|
||||||
|
in this quickstart, you'll use the
|
||||||
|
[`FetchContent` CMake module](https://cmake.org/cmake/help/latest/module/FetchContent.html).
|
||||||
|
To do this, in your project directory (`my_project`), create a file named
|
||||||
|
`CMakeLists.txt` with the following contents:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
cmake_minimum_required(VERSION 3.14)
|
||||||
|
project(my_project)
|
||||||
|
|
||||||
|
# GoogleTest requires at least C++14
|
||||||
|
set(CMAKE_CXX_STANDARD 14)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
FetchContent_Declare(
|
||||||
|
googletest
|
||||||
|
URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
|
||||||
|
)
|
||||||
|
# For Windows: Prevent overriding the parent project's compiler/linker settings
|
||||||
|
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||||
|
FetchContent_MakeAvailable(googletest)
|
||||||
|
```
|
||||||
|
|
||||||
|
The above configuration declares a dependency on GoogleTest which is downloaded
|
||||||
|
from GitHub. In the above example, `03597a01ee50ed33e9dfd640b249b4be3799d395` is
|
||||||
|
the Git commit hash of the GoogleTest version to use; we recommend updating the
|
||||||
|
hash often to point to the latest version.
|
||||||
|
|
||||||
|
For more information about how to create `CMakeLists.txt` files, see the
|
||||||
|
[CMake Tutorial](https://cmake.org/cmake/help/latest/guide/tutorial/index.html).
|
||||||
|
|
||||||
|
## Create and run a binary
|
||||||
|
|
||||||
|
With GoogleTest declared as a dependency, you can use GoogleTest code within
|
||||||
|
your own project.
|
||||||
|
|
||||||
|
As an example, create a file named `hello_test.cc` in your `my_project`
|
||||||
|
directory with the following contents:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
// Demonstrate some basic assertions.
|
||||||
|
TEST(HelloTest, BasicAssertions) {
|
||||||
|
// Expect two strings not to be equal.
|
||||||
|
EXPECT_STRNE("hello", "world");
|
||||||
|
// Expect equality.
|
||||||
|
EXPECT_EQ(7 * 6, 42);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
GoogleTest provides [assertions](primer.md#assertions) that you use to test the
|
||||||
|
behavior of your code. The above sample includes the main GoogleTest header file
|
||||||
|
and demonstrates some basic assertions.
|
||||||
|
|
||||||
|
To build the code, add the following to the end of your `CMakeLists.txt` file:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
add_executable(
|
||||||
|
hello_test
|
||||||
|
hello_test.cc
|
||||||
|
)
|
||||||
|
target_link_libraries(
|
||||||
|
hello_test
|
||||||
|
GTest::gtest_main
|
||||||
|
)
|
||||||
|
|
||||||
|
include(GoogleTest)
|
||||||
|
gtest_discover_tests(hello_test)
|
||||||
|
```
|
||||||
|
|
||||||
|
The above configuration enables testing in CMake, declares the C++ test binary
|
||||||
|
you want to build (`hello_test`), and links it to GoogleTest (`gtest_main`). The
|
||||||
|
last two lines enable CMake's test runner to discover the tests included in the
|
||||||
|
binary, using the
|
||||||
|
[`GoogleTest` CMake module](https://cmake.org/cmake/help/git-stage/module/GoogleTest.html).
|
||||||
|
|
||||||
|
Now you can build and run your test:
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<strong>my_project$ cmake -S . -B build</strong>
|
||||||
|
-- The C compiler identification is GNU 10.2.1
|
||||||
|
-- The CXX compiler identification is GNU 10.2.1
|
||||||
|
...
|
||||||
|
-- Build files have been written to: .../my_project/build
|
||||||
|
|
||||||
|
<strong>my_project$ cmake --build build</strong>
|
||||||
|
Scanning dependencies of target gtest
|
||||||
|
...
|
||||||
|
[100%] Built target gmock_main
|
||||||
|
|
||||||
|
<strong>my_project$ cd build && ctest</strong>
|
||||||
|
Test project .../my_project/build
|
||||||
|
Start 1: HelloTest.BasicAssertions
|
||||||
|
1/1 Test #1: HelloTest.BasicAssertions ........ Passed 0.00 sec
|
||||||
|
|
||||||
|
100% tests passed, 0 tests failed out of 1
|
||||||
|
|
||||||
|
Total Test time (real) = 0.01 sec
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
Congratulations! You've successfully built and run a test binary using
|
||||||
|
GoogleTest.
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
|
||||||
|
* [Check out the Primer](primer.md) to start learning how to write simple
|
||||||
|
tests.
|
||||||
|
* [See the code samples](samples.md) for more examples showing how to use a
|
||||||
|
variety of GoogleTest features.
|
|
@ -0,0 +1,115 @@
|
||||||
|
# Actions Reference
|
||||||
|
|
||||||
|
[**Actions**](../gmock_for_dummies.md#actions-what-should-it-do) specify what a
|
||||||
|
mock function should do when invoked. This page lists the built-in actions
|
||||||
|
provided by GoogleTest. All actions are defined in the `::testing` namespace.
|
||||||
|
|
||||||
|
## Returning a Value
|
||||||
|
|
||||||
|
| Action | Description |
|
||||||
|
| :-------------------------------- | :-------------------------------------------- |
|
||||||
|
| `Return()` | Return from a `void` mock function. |
|
||||||
|
| `Return(value)` | Return `value`. If the type of `value` is different to the mock function's return type, `value` is converted to the latter type <i>at the time the expectation is set</i>, not when the action is executed. |
|
||||||
|
| `ReturnArg<N>()` | Return the `N`-th (0-based) argument. |
|
||||||
|
| `ReturnNew<T>(a1, ..., ak)` | Return `new T(a1, ..., ak)`; a different object is created each time. |
|
||||||
|
| `ReturnNull()` | Return a null pointer. |
|
||||||
|
| `ReturnPointee(ptr)` | Return the value pointed to by `ptr`. |
|
||||||
|
| `ReturnRef(variable)` | Return a reference to `variable`. |
|
||||||
|
| `ReturnRefOfCopy(value)` | Return a reference to a copy of `value`; the copy lives as long as the action. |
|
||||||
|
| `ReturnRoundRobin({a1, ..., ak})` | Each call will return the next `ai` in the list, starting at the beginning when the end of the list is reached. |
|
||||||
|
|
||||||
|
## Side Effects
|
||||||
|
|
||||||
|
| Action | Description |
|
||||||
|
| :--------------------------------- | :-------------------------------------- |
|
||||||
|
| `Assign(&variable, value)` | Assign `value` to variable. |
|
||||||
|
| `DeleteArg<N>()` | Delete the `N`-th (0-based) argument, which must be a pointer. |
|
||||||
|
| `SaveArg<N>(pointer)` | Save the `N`-th (0-based) argument to `*pointer`. |
|
||||||
|
| `SaveArgPointee<N>(pointer)` | Save the value pointed to by the `N`-th (0-based) argument to `*pointer`. |
|
||||||
|
| `SetArgReferee<N>(value)` | Assign `value` to the variable referenced by the `N`-th (0-based) argument. |
|
||||||
|
| `SetArgPointee<N>(value)` | Assign `value` to the variable pointed by the `N`-th (0-based) argument. |
|
||||||
|
| `SetArgumentPointee<N>(value)` | Same as `SetArgPointee<N>(value)`. Deprecated. Will be removed in v1.7.0. |
|
||||||
|
| `SetArrayArgument<N>(first, last)` | Copies the elements in source range [`first`, `last`) to the array pointed to by the `N`-th (0-based) argument, which can be either a pointer or an iterator. The action does not take ownership of the elements in the source range. |
|
||||||
|
| `SetErrnoAndReturn(error, value)` | Set `errno` to `error` and return `value`. |
|
||||||
|
| `Throw(exception)` | Throws the given exception, which can be any copyable value. Available since v1.1.0. |
|
||||||
|
|
||||||
|
## Using a Function, Functor, or Lambda as an Action
|
||||||
|
|
||||||
|
In the following, by "callable" we mean a free function, `std::function`,
|
||||||
|
functor, or lambda.
|
||||||
|
|
||||||
|
| Action | Description |
|
||||||
|
| :---------------------------------- | :------------------------------------- |
|
||||||
|
| `f` | Invoke `f` with the arguments passed to the mock function, where `f` is a callable. |
|
||||||
|
| `Invoke(f)` | Invoke `f` with the arguments passed to the mock function, where `f` can be a global/static function or a functor. |
|
||||||
|
| `Invoke(object_pointer, &class::method)` | Invoke the method on the object with the arguments passed to the mock function. |
|
||||||
|
| `InvokeWithoutArgs(f)` | Invoke `f`, which can be a global/static function or a functor. `f` must take no arguments. |
|
||||||
|
| `InvokeWithoutArgs(object_pointer, &class::method)` | Invoke the method on the object, which takes no arguments. |
|
||||||
|
| `InvokeArgument<N>(arg1, arg2, ..., argk)` | Invoke the mock function's `N`-th (0-based) argument, which must be a function or a functor, with the `k` arguments. |
|
||||||
|
|
||||||
|
The return value of the invoked function is used as the return value of the
|
||||||
|
action.
|
||||||
|
|
||||||
|
When defining a callable to be used with `Invoke*()`, you can declare any unused
|
||||||
|
parameters as `Unused`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Invoke;
|
||||||
|
double Distance(Unused, double x, double y) { return sqrt(x*x + y*y); }
|
||||||
|
...
|
||||||
|
EXPECT_CALL(mock, Foo("Hi", _, _)).WillOnce(Invoke(Distance));
|
||||||
|
```
|
||||||
|
|
||||||
|
`Invoke(callback)` and `InvokeWithoutArgs(callback)` take ownership of
|
||||||
|
`callback`, which must be permanent. The type of `callback` must be a base
|
||||||
|
callback type instead of a derived one, e.g.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
BlockingClosure* done = new BlockingClosure;
|
||||||
|
... Invoke(done) ...; // This won't compile!
|
||||||
|
|
||||||
|
Closure* done2 = new BlockingClosure;
|
||||||
|
... Invoke(done2) ...; // This works.
|
||||||
|
```
|
||||||
|
|
||||||
|
In `InvokeArgument<N>(...)`, if an argument needs to be passed by reference,
|
||||||
|
wrap it inside `std::ref()`. For example,
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::InvokeArgument;
|
||||||
|
...
|
||||||
|
InvokeArgument<2>(5, string("Hi"), std::ref(foo))
|
||||||
|
```
|
||||||
|
|
||||||
|
calls the mock function's #2 argument, passing to it `5` and `string("Hi")` by
|
||||||
|
value, and `foo` by reference.
|
||||||
|
|
||||||
|
## Default Action
|
||||||
|
|
||||||
|
| Action | Description |
|
||||||
|
| :------------ | :----------------------------------------------------- |
|
||||||
|
| `DoDefault()` | Do the default action (specified by `ON_CALL()` or the built-in one). |
|
||||||
|
|
||||||
|
{: .callout .note}
|
||||||
|
**Note:** due to technical reasons, `DoDefault()` cannot be used inside a
|
||||||
|
composite action - trying to do so will result in a run-time error.
|
||||||
|
|
||||||
|
## Composite Actions
|
||||||
|
|
||||||
|
| Action | Description |
|
||||||
|
| :----------------------------- | :------------------------------------------ |
|
||||||
|
| `DoAll(a1, a2, ..., an)` | Do all actions `a1` to `an` and return the result of `an` in each invocation. The first `n - 1` sub-actions must return void and will receive a readonly view of the arguments. |
|
||||||
|
| `IgnoreResult(a)` | Perform action `a` and ignore its result. `a` must not return void. |
|
||||||
|
| `WithArg<N>(a)` | Pass the `N`-th (0-based) argument of the mock function to action `a` and perform it. |
|
||||||
|
| `WithArgs<N1, N2, ..., Nk>(a)` | Pass the selected (0-based) arguments of the mock function to action `a` and perform it. |
|
||||||
|
| `WithoutArgs(a)` | Perform action `a` without any arguments. |
|
||||||
|
|
||||||
|
## Defining Actions
|
||||||
|
|
||||||
|
| Macro | Description |
|
||||||
|
| :--------------------------------- | :-------------------------------------- |
|
||||||
|
| `ACTION(Sum) { return arg0 + arg1; }` | Defines an action `Sum()` to return the sum of the mock function's argument #0 and #1. |
|
||||||
|
| `ACTION_P(Plus, n) { return arg0 + n; }` | Defines an action `Plus(n)` to return the sum of the mock function's argument #0 and `n`. |
|
||||||
|
| `ACTION_Pk(Foo, p1, ..., pk) { statements; }` | Defines a parameterized action `Foo(p1, ..., pk)` to execute the given `statements`. |
|
||||||
|
|
||||||
|
The `ACTION*` macros cannot be used inside a function or class.
|
|
@ -0,0 +1,633 @@
|
||||||
|
# Assertions Reference
|
||||||
|
|
||||||
|
This page lists the assertion macros provided by GoogleTest for verifying code
|
||||||
|
behavior. To use them, include the header `gtest/gtest.h`.
|
||||||
|
|
||||||
|
The majority of the macros listed below come as a pair with an `EXPECT_` variant
|
||||||
|
and an `ASSERT_` variant. Upon failure, `EXPECT_` macros generate nonfatal
|
||||||
|
failures and allow the current function to continue running, while `ASSERT_`
|
||||||
|
macros generate fatal failures and abort the current function.
|
||||||
|
|
||||||
|
All assertion macros support streaming a custom failure message into them with
|
||||||
|
the `<<` operator, for example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
EXPECT_TRUE(my_condition) << "My condition is not true";
|
||||||
|
```
|
||||||
|
|
||||||
|
Anything that can be streamed to an `ostream` can be streamed to an assertion
|
||||||
|
macro—in particular, C strings and string objects. If a wide string (`wchar_t*`,
|
||||||
|
`TCHAR*` in `UNICODE` mode on Windows, or `std::wstring`) is streamed to an
|
||||||
|
assertion, it will be translated to UTF-8 when printed.
|
||||||
|
|
||||||
|
## Explicit Success and Failure {#success-failure}
|
||||||
|
|
||||||
|
The assertions in this section generate a success or failure directly instead of
|
||||||
|
testing a value or expression. These are useful when control flow, rather than a
|
||||||
|
Boolean expression, determines the test's success or failure, as shown by the
|
||||||
|
following example:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
switch(expression) {
|
||||||
|
case 1:
|
||||||
|
... some checks ...
|
||||||
|
case 2:
|
||||||
|
... some other checks ...
|
||||||
|
default:
|
||||||
|
FAIL() << "We shouldn't get here.";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### SUCCEED {#SUCCEED}
|
||||||
|
|
||||||
|
`SUCCEED()`
|
||||||
|
|
||||||
|
Generates a success. This *does not* make the overall test succeed. A test is
|
||||||
|
considered successful only if none of its assertions fail during its execution.
|
||||||
|
|
||||||
|
The `SUCCEED` assertion is purely documentary and currently doesn't generate any
|
||||||
|
user-visible output. However, we may add `SUCCEED` messages to GoogleTest output
|
||||||
|
in the future.
|
||||||
|
|
||||||
|
### FAIL {#FAIL}
|
||||||
|
|
||||||
|
`FAIL()`
|
||||||
|
|
||||||
|
Generates a fatal failure, which returns from the current function.
|
||||||
|
|
||||||
|
Can only be used in functions that return `void`. See
|
||||||
|
[Assertion Placement](../advanced.md#assertion-placement) for more information.
|
||||||
|
|
||||||
|
### ADD_FAILURE {#ADD_FAILURE}
|
||||||
|
|
||||||
|
`ADD_FAILURE()`
|
||||||
|
|
||||||
|
Generates a nonfatal failure, which allows the current function to continue
|
||||||
|
running.
|
||||||
|
|
||||||
|
### ADD_FAILURE_AT {#ADD_FAILURE_AT}
|
||||||
|
|
||||||
|
`ADD_FAILURE_AT(`*`file_path`*`,`*`line_number`*`)`
|
||||||
|
|
||||||
|
Generates a nonfatal failure at the file and line number specified.
|
||||||
|
|
||||||
|
## Generalized Assertion {#generalized}
|
||||||
|
|
||||||
|
The following assertion allows [matchers](matchers.md) to be used to verify
|
||||||
|
values.
|
||||||
|
|
||||||
|
### EXPECT_THAT {#EXPECT_THAT}
|
||||||
|
|
||||||
|
`EXPECT_THAT(`*`value`*`,`*`matcher`*`)` \
|
||||||
|
`ASSERT_THAT(`*`value`*`,`*`matcher`*`)`
|
||||||
|
|
||||||
|
Verifies that *`value`* matches the [matcher](matchers.md) *`matcher`*.
|
||||||
|
|
||||||
|
For example, the following code verifies that the string `value1` starts with
|
||||||
|
`"Hello"`, `value2` matches a regular expression, and `value3` is between 5 and
|
||||||
|
10:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
|
using ::testing::AllOf;
|
||||||
|
using ::testing::Gt;
|
||||||
|
using ::testing::Lt;
|
||||||
|
using ::testing::MatchesRegex;
|
||||||
|
using ::testing::StartsWith;
|
||||||
|
|
||||||
|
...
|
||||||
|
EXPECT_THAT(value1, StartsWith("Hello"));
|
||||||
|
EXPECT_THAT(value2, MatchesRegex("Line \\d+"));
|
||||||
|
ASSERT_THAT(value3, AllOf(Gt(5), Lt(10)));
|
||||||
|
```
|
||||||
|
|
||||||
|
Matchers enable assertions of this form to read like English and generate
|
||||||
|
informative failure messages. For example, if the above assertion on `value1`
|
||||||
|
fails, the resulting message will be similar to the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
Value of: value1
|
||||||
|
Actual: "Hi, world!"
|
||||||
|
Expected: starts with "Hello"
|
||||||
|
```
|
||||||
|
|
||||||
|
GoogleTest provides a built-in library of matchers—see the
|
||||||
|
[Matchers Reference](matchers.md). It is also possible to write your own
|
||||||
|
matchers—see [Writing New Matchers Quickly](../gmock_cook_book.md#NewMatchers).
|
||||||
|
The use of matchers makes `EXPECT_THAT` a powerful, extensible assertion.
|
||||||
|
|
||||||
|
*The idea for this assertion was borrowed from Joe Walnes' Hamcrest project,
|
||||||
|
which adds `assertThat()` to JUnit.*
|
||||||
|
|
||||||
|
## Boolean Conditions {#boolean}
|
||||||
|
|
||||||
|
The following assertions test Boolean conditions.
|
||||||
|
|
||||||
|
### EXPECT_TRUE {#EXPECT_TRUE}
|
||||||
|
|
||||||
|
`EXPECT_TRUE(`*`condition`*`)` \
|
||||||
|
`ASSERT_TRUE(`*`condition`*`)`
|
||||||
|
|
||||||
|
Verifies that *`condition`* is true.
|
||||||
|
|
||||||
|
### EXPECT_FALSE {#EXPECT_FALSE}
|
||||||
|
|
||||||
|
`EXPECT_FALSE(`*`condition`*`)` \
|
||||||
|
`ASSERT_FALSE(`*`condition`*`)`
|
||||||
|
|
||||||
|
Verifies that *`condition`* is false.
|
||||||
|
|
||||||
|
## Binary Comparison {#binary-comparison}
|
||||||
|
|
||||||
|
The following assertions compare two values. The value arguments must be
|
||||||
|
comparable by the assertion's comparison operator, otherwise a compiler error
|
||||||
|
will result.
|
||||||
|
|
||||||
|
If an argument supports the `<<` operator, it will be called to print the
|
||||||
|
argument when the assertion fails. Otherwise, GoogleTest will attempt to print
|
||||||
|
them in the best way it can—see
|
||||||
|
[Teaching GoogleTest How to Print Your Values](../advanced.md#teaching-googletest-how-to-print-your-values).
|
||||||
|
|
||||||
|
Arguments are always evaluated exactly once, so it's OK for the arguments to
|
||||||
|
have side effects. However, the argument evaluation order is undefined and
|
||||||
|
programs should not depend on any particular argument evaluation order.
|
||||||
|
|
||||||
|
These assertions work with both narrow and wide string objects (`string` and
|
||||||
|
`wstring`).
|
||||||
|
|
||||||
|
See also the [Floating-Point Comparison](#floating-point) assertions to compare
|
||||||
|
floating-point numbers and avoid problems caused by rounding.
|
||||||
|
|
||||||
|
### EXPECT_EQ {#EXPECT_EQ}
|
||||||
|
|
||||||
|
`EXPECT_EQ(`*`val1`*`,`*`val2`*`)` \
|
||||||
|
`ASSERT_EQ(`*`val1`*`,`*`val2`*`)`
|
||||||
|
|
||||||
|
Verifies that *`val1`*`==`*`val2`*.
|
||||||
|
|
||||||
|
Does pointer equality on pointers. If used on two C strings, it tests if they
|
||||||
|
are in the same memory location, not if they have the same value. Use
|
||||||
|
[`EXPECT_STREQ`](#EXPECT_STREQ) to compare C strings (e.g. `const char*`) by
|
||||||
|
value.
|
||||||
|
|
||||||
|
When comparing a pointer to `NULL`, use `EXPECT_EQ(`*`ptr`*`, nullptr)` instead
|
||||||
|
of `EXPECT_EQ(`*`ptr`*`, NULL)`.
|
||||||
|
|
||||||
|
### EXPECT_NE {#EXPECT_NE}
|
||||||
|
|
||||||
|
`EXPECT_NE(`*`val1`*`,`*`val2`*`)` \
|
||||||
|
`ASSERT_NE(`*`val1`*`,`*`val2`*`)`
|
||||||
|
|
||||||
|
Verifies that *`val1`*`!=`*`val2`*.
|
||||||
|
|
||||||
|
Does pointer equality on pointers. If used on two C strings, it tests if they
|
||||||
|
are in different memory locations, not if they have different values. Use
|
||||||
|
[`EXPECT_STRNE`](#EXPECT_STRNE) to compare C strings (e.g. `const char*`) by
|
||||||
|
value.
|
||||||
|
|
||||||
|
When comparing a pointer to `NULL`, use `EXPECT_NE(`*`ptr`*`, nullptr)` instead
|
||||||
|
of `EXPECT_NE(`*`ptr`*`, NULL)`.
|
||||||
|
|
||||||
|
### EXPECT_LT {#EXPECT_LT}
|
||||||
|
|
||||||
|
`EXPECT_LT(`*`val1`*`,`*`val2`*`)` \
|
||||||
|
`ASSERT_LT(`*`val1`*`,`*`val2`*`)`
|
||||||
|
|
||||||
|
Verifies that *`val1`*`<`*`val2`*.
|
||||||
|
|
||||||
|
### EXPECT_LE {#EXPECT_LE}
|
||||||
|
|
||||||
|
`EXPECT_LE(`*`val1`*`,`*`val2`*`)` \
|
||||||
|
`ASSERT_LE(`*`val1`*`,`*`val2`*`)`
|
||||||
|
|
||||||
|
Verifies that *`val1`*`<=`*`val2`*.
|
||||||
|
|
||||||
|
### EXPECT_GT {#EXPECT_GT}
|
||||||
|
|
||||||
|
`EXPECT_GT(`*`val1`*`,`*`val2`*`)` \
|
||||||
|
`ASSERT_GT(`*`val1`*`,`*`val2`*`)`
|
||||||
|
|
||||||
|
Verifies that *`val1`*`>`*`val2`*.
|
||||||
|
|
||||||
|
### EXPECT_GE {#EXPECT_GE}
|
||||||
|
|
||||||
|
`EXPECT_GE(`*`val1`*`,`*`val2`*`)` \
|
||||||
|
`ASSERT_GE(`*`val1`*`,`*`val2`*`)`
|
||||||
|
|
||||||
|
Verifies that *`val1`*`>=`*`val2`*.
|
||||||
|
|
||||||
|
## String Comparison {#c-strings}
|
||||||
|
|
||||||
|
The following assertions compare two **C strings**. To compare two `string`
|
||||||
|
objects, use [`EXPECT_EQ`](#EXPECT_EQ) or [`EXPECT_NE`](#EXPECT_NE) instead.
|
||||||
|
|
||||||
|
These assertions also accept wide C strings (`wchar_t*`). If a comparison of two
|
||||||
|
wide strings fails, their values will be printed as UTF-8 narrow strings.
|
||||||
|
|
||||||
|
To compare a C string with `NULL`, use `EXPECT_EQ(`*`c_string`*`, nullptr)` or
|
||||||
|
`EXPECT_NE(`*`c_string`*`, nullptr)`.
|
||||||
|
|
||||||
|
### EXPECT_STREQ {#EXPECT_STREQ}
|
||||||
|
|
||||||
|
`EXPECT_STREQ(`*`str1`*`,`*`str2`*`)` \
|
||||||
|
`ASSERT_STREQ(`*`str1`*`,`*`str2`*`)`
|
||||||
|
|
||||||
|
Verifies that the two C strings *`str1`* and *`str2`* have the same contents.
|
||||||
|
|
||||||
|
### EXPECT_STRNE {#EXPECT_STRNE}
|
||||||
|
|
||||||
|
`EXPECT_STRNE(`*`str1`*`,`*`str2`*`)` \
|
||||||
|
`ASSERT_STRNE(`*`str1`*`,`*`str2`*`)`
|
||||||
|
|
||||||
|
Verifies that the two C strings *`str1`* and *`str2`* have different contents.
|
||||||
|
|
||||||
|
### EXPECT_STRCASEEQ {#EXPECT_STRCASEEQ}
|
||||||
|
|
||||||
|
`EXPECT_STRCASEEQ(`*`str1`*`,`*`str2`*`)` \
|
||||||
|
`ASSERT_STRCASEEQ(`*`str1`*`,`*`str2`*`)`
|
||||||
|
|
||||||
|
Verifies that the two C strings *`str1`* and *`str2`* have the same contents,
|
||||||
|
ignoring case.
|
||||||
|
|
||||||
|
### EXPECT_STRCASENE {#EXPECT_STRCASENE}
|
||||||
|
|
||||||
|
`EXPECT_STRCASENE(`*`str1`*`,`*`str2`*`)` \
|
||||||
|
`ASSERT_STRCASENE(`*`str1`*`,`*`str2`*`)`
|
||||||
|
|
||||||
|
Verifies that the two C strings *`str1`* and *`str2`* have different contents,
|
||||||
|
ignoring case.
|
||||||
|
|
||||||
|
## Floating-Point Comparison {#floating-point}
|
||||||
|
|
||||||
|
The following assertions compare two floating-point values.
|
||||||
|
|
||||||
|
Due to rounding errors, it is very unlikely that two floating-point values will
|
||||||
|
match exactly, so `EXPECT_EQ` is not suitable. In general, for floating-point
|
||||||
|
comparison to make sense, the user needs to carefully choose the error bound.
|
||||||
|
|
||||||
|
GoogleTest also provides assertions that use a default error bound based on
|
||||||
|
Units in the Last Place (ULPs). To learn more about ULPs, see the article
|
||||||
|
[Comparing Floating Point Numbers](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/).
|
||||||
|
|
||||||
|
### EXPECT_FLOAT_EQ {#EXPECT_FLOAT_EQ}
|
||||||
|
|
||||||
|
`EXPECT_FLOAT_EQ(`*`val1`*`,`*`val2`*`)` \
|
||||||
|
`ASSERT_FLOAT_EQ(`*`val1`*`,`*`val2`*`)`
|
||||||
|
|
||||||
|
Verifies that the two `float` values *`val1`* and *`val2`* are approximately
|
||||||
|
equal, to within 4 ULPs from each other.
|
||||||
|
|
||||||
|
### EXPECT_DOUBLE_EQ {#EXPECT_DOUBLE_EQ}
|
||||||
|
|
||||||
|
`EXPECT_DOUBLE_EQ(`*`val1`*`,`*`val2`*`)` \
|
||||||
|
`ASSERT_DOUBLE_EQ(`*`val1`*`,`*`val2`*`)`
|
||||||
|
|
||||||
|
Verifies that the two `double` values *`val1`* and *`val2`* are approximately
|
||||||
|
equal, to within 4 ULPs from each other.
|
||||||
|
|
||||||
|
### EXPECT_NEAR {#EXPECT_NEAR}
|
||||||
|
|
||||||
|
`EXPECT_NEAR(`*`val1`*`,`*`val2`*`,`*`abs_error`*`)` \
|
||||||
|
`ASSERT_NEAR(`*`val1`*`,`*`val2`*`,`*`abs_error`*`)`
|
||||||
|
|
||||||
|
Verifies that the difference between *`val1`* and *`val2`* does not exceed the
|
||||||
|
absolute error bound *`abs_error`*.
|
||||||
|
|
||||||
|
## Exception Assertions {#exceptions}
|
||||||
|
|
||||||
|
The following assertions verify that a piece of code throws, or does not throw,
|
||||||
|
an exception. Usage requires exceptions to be enabled in the build environment.
|
||||||
|
|
||||||
|
Note that the piece of code under test can be a compound statement, for example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
EXPECT_NO_THROW({
|
||||||
|
int n = 5;
|
||||||
|
DoSomething(&n);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### EXPECT_THROW {#EXPECT_THROW}
|
||||||
|
|
||||||
|
`EXPECT_THROW(`*`statement`*`,`*`exception_type`*`)` \
|
||||||
|
`ASSERT_THROW(`*`statement`*`,`*`exception_type`*`)`
|
||||||
|
|
||||||
|
Verifies that *`statement`* throws an exception of type *`exception_type`*.
|
||||||
|
|
||||||
|
### EXPECT_ANY_THROW {#EXPECT_ANY_THROW}
|
||||||
|
|
||||||
|
`EXPECT_ANY_THROW(`*`statement`*`)` \
|
||||||
|
`ASSERT_ANY_THROW(`*`statement`*`)`
|
||||||
|
|
||||||
|
Verifies that *`statement`* throws an exception of any type.
|
||||||
|
|
||||||
|
### EXPECT_NO_THROW {#EXPECT_NO_THROW}
|
||||||
|
|
||||||
|
`EXPECT_NO_THROW(`*`statement`*`)` \
|
||||||
|
`ASSERT_NO_THROW(`*`statement`*`)`
|
||||||
|
|
||||||
|
Verifies that *`statement`* does not throw any exception.
|
||||||
|
|
||||||
|
## Predicate Assertions {#predicates}
|
||||||
|
|
||||||
|
The following assertions enable more complex predicates to be verified while
|
||||||
|
printing a more clear failure message than if `EXPECT_TRUE` were used alone.
|
||||||
|
|
||||||
|
### EXPECT_PRED* {#EXPECT_PRED}
|
||||||
|
|
||||||
|
`EXPECT_PRED1(`*`pred`*`,`*`val1`*`)` \
|
||||||
|
`EXPECT_PRED2(`*`pred`*`,`*`val1`*`,`*`val2`*`)` \
|
||||||
|
`EXPECT_PRED3(`*`pred`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`)` \
|
||||||
|
`EXPECT_PRED4(`*`pred`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`,`*`val4`*`)` \
|
||||||
|
`EXPECT_PRED5(`*`pred`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`,`*`val4`*`,`*`val5`*`)`
|
||||||
|
|
||||||
|
`ASSERT_PRED1(`*`pred`*`,`*`val1`*`)` \
|
||||||
|
`ASSERT_PRED2(`*`pred`*`,`*`val1`*`,`*`val2`*`)` \
|
||||||
|
`ASSERT_PRED3(`*`pred`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`)` \
|
||||||
|
`ASSERT_PRED4(`*`pred`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`,`*`val4`*`)` \
|
||||||
|
`ASSERT_PRED5(`*`pred`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`,`*`val4`*`,`*`val5`*`)`
|
||||||
|
|
||||||
|
Verifies that the predicate *`pred`* returns `true` when passed the given values
|
||||||
|
as arguments.
|
||||||
|
|
||||||
|
The parameter *`pred`* is a function or functor that accepts as many arguments
|
||||||
|
as the corresponding macro accepts values. If *`pred`* returns `true` for the
|
||||||
|
given arguments, the assertion succeeds, otherwise the assertion fails.
|
||||||
|
|
||||||
|
When the assertion fails, it prints the value of each argument. Arguments are
|
||||||
|
always evaluated exactly once.
|
||||||
|
|
||||||
|
As an example, see the following code:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Returns true if m and n have no common divisors except 1.
|
||||||
|
bool MutuallyPrime(int m, int n) { ... }
|
||||||
|
...
|
||||||
|
const int a = 3;
|
||||||
|
const int b = 4;
|
||||||
|
const int c = 10;
|
||||||
|
...
|
||||||
|
EXPECT_PRED2(MutuallyPrime, a, b); // Succeeds
|
||||||
|
EXPECT_PRED2(MutuallyPrime, b, c); // Fails
|
||||||
|
```
|
||||||
|
|
||||||
|
In the above example, the first assertion succeeds, and the second fails with
|
||||||
|
the following message:
|
||||||
|
|
||||||
|
```
|
||||||
|
MutuallyPrime(b, c) is false, where
|
||||||
|
b is 4
|
||||||
|
c is 10
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that if the given predicate is an overloaded function or a function
|
||||||
|
template, the assertion macro might not be able to determine which version to
|
||||||
|
use, and it might be necessary to explicitly specify the type of the function.
|
||||||
|
For example, for a Boolean function `IsPositive()` overloaded to take either a
|
||||||
|
single `int` or `double` argument, it would be necessary to write one of the
|
||||||
|
following:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
EXPECT_PRED1(static_cast<bool (*)(int)>(IsPositive), 5);
|
||||||
|
EXPECT_PRED1(static_cast<bool (*)(double)>(IsPositive), 3.14);
|
||||||
|
```
|
||||||
|
|
||||||
|
Writing simply `EXPECT_PRED1(IsPositive, 5);` would result in a compiler error.
|
||||||
|
Similarly, to use a template function, specify the template arguments:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template <typename T>
|
||||||
|
bool IsNegative(T x) {
|
||||||
|
return x < 0;
|
||||||
|
}
|
||||||
|
...
|
||||||
|
EXPECT_PRED1(IsNegative<int>, -5); // Must specify type for IsNegative
|
||||||
|
```
|
||||||
|
|
||||||
|
If a template has multiple parameters, wrap the predicate in parentheses so the
|
||||||
|
macro arguments are parsed correctly:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
ASSERT_PRED2((MyPredicate<int, int>), 5, 0);
|
||||||
|
```
|
||||||
|
|
||||||
|
### EXPECT_PRED_FORMAT* {#EXPECT_PRED_FORMAT}
|
||||||
|
|
||||||
|
`EXPECT_PRED_FORMAT1(`*`pred_formatter`*`,`*`val1`*`)` \
|
||||||
|
`EXPECT_PRED_FORMAT2(`*`pred_formatter`*`,`*`val1`*`,`*`val2`*`)` \
|
||||||
|
`EXPECT_PRED_FORMAT3(`*`pred_formatter`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`)` \
|
||||||
|
`EXPECT_PRED_FORMAT4(`*`pred_formatter`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`,`*`val4`*`)`
|
||||||
|
\
|
||||||
|
`EXPECT_PRED_FORMAT5(`*`pred_formatter`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`,`*`val4`*`,`*`val5`*`)`
|
||||||
|
|
||||||
|
`ASSERT_PRED_FORMAT1(`*`pred_formatter`*`,`*`val1`*`)` \
|
||||||
|
`ASSERT_PRED_FORMAT2(`*`pred_formatter`*`,`*`val1`*`,`*`val2`*`)` \
|
||||||
|
`ASSERT_PRED_FORMAT3(`*`pred_formatter`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`)` \
|
||||||
|
`ASSERT_PRED_FORMAT4(`*`pred_formatter`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`,`*`val4`*`)`
|
||||||
|
\
|
||||||
|
`ASSERT_PRED_FORMAT5(`*`pred_formatter`*`,`*`val1`*`,`*`val2`*`,`*`val3`*`,`*`val4`*`,`*`val5`*`)`
|
||||||
|
|
||||||
|
Verifies that the predicate *`pred_formatter`* succeeds when passed the given
|
||||||
|
values as arguments.
|
||||||
|
|
||||||
|
The parameter *`pred_formatter`* is a *predicate-formatter*, which is a function
|
||||||
|
or functor with the signature:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
testing::AssertionResult PredicateFormatter(const char* expr1,
|
||||||
|
const char* expr2,
|
||||||
|
...
|
||||||
|
const char* exprn,
|
||||||
|
T1 val1,
|
||||||
|
T2 val2,
|
||||||
|
...
|
||||||
|
Tn valn);
|
||||||
|
```
|
||||||
|
|
||||||
|
where *`val1`*, *`val2`*, ..., *`valn`* are the values of the predicate
|
||||||
|
arguments, and *`expr1`*, *`expr2`*, ..., *`exprn`* are the corresponding
|
||||||
|
expressions as they appear in the source code. The types `T1`, `T2`, ..., `Tn`
|
||||||
|
can be either value types or reference types; if an argument has type `T`, it
|
||||||
|
can be declared as either `T` or `const T&`, whichever is appropriate. For more
|
||||||
|
about the return type `testing::AssertionResult`, see
|
||||||
|
[Using a Function That Returns an AssertionResult](../advanced.md#using-a-function-that-returns-an-assertionresult).
|
||||||
|
|
||||||
|
As an example, see the following code:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Returns the smallest prime common divisor of m and n,
|
||||||
|
// or 1 when m and n are mutually prime.
|
||||||
|
int SmallestPrimeCommonDivisor(int m, int n) { ... }
|
||||||
|
|
||||||
|
// Returns true if m and n have no common divisors except 1.
|
||||||
|
bool MutuallyPrime(int m, int n) { ... }
|
||||||
|
|
||||||
|
// A predicate-formatter for asserting that two integers are mutually prime.
|
||||||
|
testing::AssertionResult AssertMutuallyPrime(const char* m_expr,
|
||||||
|
const char* n_expr,
|
||||||
|
int m,
|
||||||
|
int n) {
|
||||||
|
if (MutuallyPrime(m, n)) return testing::AssertionSuccess();
|
||||||
|
|
||||||
|
return testing::AssertionFailure() << m_expr << " and " << n_expr
|
||||||
|
<< " (" << m << " and " << n << ") are not mutually prime, "
|
||||||
|
<< "as they have a common divisor " << SmallestPrimeCommonDivisor(m, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
...
|
||||||
|
const int a = 3;
|
||||||
|
const int b = 4;
|
||||||
|
const int c = 10;
|
||||||
|
...
|
||||||
|
EXPECT_PRED_FORMAT2(AssertMutuallyPrime, a, b); // Succeeds
|
||||||
|
EXPECT_PRED_FORMAT2(AssertMutuallyPrime, b, c); // Fails
|
||||||
|
```
|
||||||
|
|
||||||
|
In the above example, the final assertion fails and the predicate-formatter
|
||||||
|
produces the following failure message:
|
||||||
|
|
||||||
|
```
|
||||||
|
b and c (4 and 10) are not mutually prime, as they have a common divisor 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Windows HRESULT Assertions {#HRESULT}
|
||||||
|
|
||||||
|
The following assertions test for `HRESULT` success or failure. For example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
CComPtr<IShellDispatch2> shell;
|
||||||
|
ASSERT_HRESULT_SUCCEEDED(shell.CoCreateInstance(L"Shell.Application"));
|
||||||
|
CComVariant empty;
|
||||||
|
ASSERT_HRESULT_SUCCEEDED(shell->ShellExecute(CComBSTR(url), empty, empty, empty, empty));
|
||||||
|
```
|
||||||
|
|
||||||
|
The generated output contains the human-readable error message associated with
|
||||||
|
the returned `HRESULT` code.
|
||||||
|
|
||||||
|
### EXPECT_HRESULT_SUCCEEDED {#EXPECT_HRESULT_SUCCEEDED}
|
||||||
|
|
||||||
|
`EXPECT_HRESULT_SUCCEEDED(`*`expression`*`)` \
|
||||||
|
`ASSERT_HRESULT_SUCCEEDED(`*`expression`*`)`
|
||||||
|
|
||||||
|
Verifies that *`expression`* is a success `HRESULT`.
|
||||||
|
|
||||||
|
### EXPECT_HRESULT_FAILED {#EXPECT_HRESULT_FAILED}
|
||||||
|
|
||||||
|
`EXPECT_HRESULT_FAILED(`*`expression`*`)` \
|
||||||
|
`ASSERT_HRESULT_FAILED(`*`expression`*`)`
|
||||||
|
|
||||||
|
Verifies that *`expression`* is a failure `HRESULT`.
|
||||||
|
|
||||||
|
## Death Assertions {#death}
|
||||||
|
|
||||||
|
The following assertions verify that a piece of code causes the process to
|
||||||
|
terminate. For context, see [Death Tests](../advanced.md#death-tests).
|
||||||
|
|
||||||
|
These assertions spawn a new process and execute the code under test in that
|
||||||
|
process. How that happens depends on the platform and the variable
|
||||||
|
`::testing::GTEST_FLAG(death_test_style)`, which is initialized from the
|
||||||
|
command-line flag `--gtest_death_test_style`.
|
||||||
|
|
||||||
|
* On POSIX systems, `fork()` (or `clone()` on Linux) is used to spawn the
|
||||||
|
child, after which:
|
||||||
|
* If the variable's value is `"fast"`, the death test statement is
|
||||||
|
immediately executed.
|
||||||
|
* If the variable's value is `"threadsafe"`, the child process re-executes
|
||||||
|
the unit test binary just as it was originally invoked, but with some
|
||||||
|
extra flags to cause just the single death test under consideration to
|
||||||
|
be run.
|
||||||
|
* On Windows, the child is spawned using the `CreateProcess()` API, and
|
||||||
|
re-executes the binary to cause just the single death test under
|
||||||
|
consideration to be run - much like the `"threadsafe"` mode on POSIX.
|
||||||
|
|
||||||
|
Other values for the variable are illegal and will cause the death test to fail.
|
||||||
|
Currently, the flag's default value is
|
||||||
|
**`"fast"`**.
|
||||||
|
|
||||||
|
If the death test statement runs to completion without dying, the child process
|
||||||
|
will nonetheless terminate, and the assertion fails.
|
||||||
|
|
||||||
|
Note that the piece of code under test can be a compound statement, for example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
EXPECT_DEATH({
|
||||||
|
int n = 5;
|
||||||
|
DoSomething(&n);
|
||||||
|
}, "Error on line .* of DoSomething()");
|
||||||
|
```
|
||||||
|
|
||||||
|
### EXPECT_DEATH {#EXPECT_DEATH}
|
||||||
|
|
||||||
|
`EXPECT_DEATH(`*`statement`*`,`*`matcher`*`)` \
|
||||||
|
`ASSERT_DEATH(`*`statement`*`,`*`matcher`*`)`
|
||||||
|
|
||||||
|
Verifies that *`statement`* causes the process to terminate with a nonzero exit
|
||||||
|
status and produces `stderr` output that matches *`matcher`*.
|
||||||
|
|
||||||
|
The parameter *`matcher`* is either a [matcher](matchers.md) for a `const
|
||||||
|
std::string&`, or a regular expression (see
|
||||||
|
[Regular Expression Syntax](../advanced.md#regular-expression-syntax))—a bare
|
||||||
|
string *`s`* (with no matcher) is treated as
|
||||||
|
[`ContainsRegex(s)`](matchers.md#string-matchers), **not**
|
||||||
|
[`Eq(s)`](matchers.md#generic-comparison).
|
||||||
|
|
||||||
|
For example, the following code verifies that calling `DoSomething(42)` causes
|
||||||
|
the process to die with an error message that contains the text `My error`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
EXPECT_DEATH(DoSomething(42), "My error");
|
||||||
|
```
|
||||||
|
|
||||||
|
### EXPECT_DEATH_IF_SUPPORTED {#EXPECT_DEATH_IF_SUPPORTED}
|
||||||
|
|
||||||
|
`EXPECT_DEATH_IF_SUPPORTED(`*`statement`*`,`*`matcher`*`)` \
|
||||||
|
`ASSERT_DEATH_IF_SUPPORTED(`*`statement`*`,`*`matcher`*`)`
|
||||||
|
|
||||||
|
If death tests are supported, behaves the same as
|
||||||
|
[`EXPECT_DEATH`](#EXPECT_DEATH). Otherwise, verifies nothing.
|
||||||
|
|
||||||
|
### EXPECT_DEBUG_DEATH {#EXPECT_DEBUG_DEATH}
|
||||||
|
|
||||||
|
`EXPECT_DEBUG_DEATH(`*`statement`*`,`*`matcher`*`)` \
|
||||||
|
`ASSERT_DEBUG_DEATH(`*`statement`*`,`*`matcher`*`)`
|
||||||
|
|
||||||
|
In debug mode, behaves the same as [`EXPECT_DEATH`](#EXPECT_DEATH). When not in
|
||||||
|
debug mode (i.e. `NDEBUG` is defined), just executes *`statement`*.
|
||||||
|
|
||||||
|
### EXPECT_EXIT {#EXPECT_EXIT}
|
||||||
|
|
||||||
|
`EXPECT_EXIT(`*`statement`*`,`*`predicate`*`,`*`matcher`*`)` \
|
||||||
|
`ASSERT_EXIT(`*`statement`*`,`*`predicate`*`,`*`matcher`*`)`
|
||||||
|
|
||||||
|
Verifies that *`statement`* causes the process to terminate with an exit status
|
||||||
|
that satisfies *`predicate`*, and produces `stderr` output that matches
|
||||||
|
*`matcher`*.
|
||||||
|
|
||||||
|
The parameter *`predicate`* is a function or functor that accepts an `int` exit
|
||||||
|
status and returns a `bool`. GoogleTest provides two predicates to handle common
|
||||||
|
cases:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Returns true if the program exited normally with the given exit status code.
|
||||||
|
::testing::ExitedWithCode(exit_code);
|
||||||
|
|
||||||
|
// Returns true if the program was killed by the given signal.
|
||||||
|
// Not available on Windows.
|
||||||
|
::testing::KilledBySignal(signal_number);
|
||||||
|
```
|
||||||
|
|
||||||
|
The parameter *`matcher`* is either a [matcher](matchers.md) for a `const
|
||||||
|
std::string&`, or a regular expression (see
|
||||||
|
[Regular Expression Syntax](../advanced.md#regular-expression-syntax))—a bare
|
||||||
|
string *`s`* (with no matcher) is treated as
|
||||||
|
[`ContainsRegex(s)`](matchers.md#string-matchers), **not**
|
||||||
|
[`Eq(s)`](matchers.md#generic-comparison).
|
||||||
|
|
||||||
|
For example, the following code verifies that calling `NormalExit()` causes the
|
||||||
|
process to print a message containing the text `Success` to `stderr` and exit
|
||||||
|
with exit status code 0:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
EXPECT_EXIT(NormalExit(), testing::ExitedWithCode(0), "Success");
|
||||||
|
```
|
|
@ -0,0 +1,302 @@
|
||||||
|
# Matchers Reference
|
||||||
|
|
||||||
|
A **matcher** matches a *single* argument. You can use it inside `ON_CALL()` or
|
||||||
|
`EXPECT_CALL()`, or use it to validate a value directly using two macros:
|
||||||
|
|
||||||
|
| Macro | Description |
|
||||||
|
| :----------------------------------- | :------------------------------------ |
|
||||||
|
| `EXPECT_THAT(actual_value, matcher)` | Asserts that `actual_value` matches `matcher`. |
|
||||||
|
| `ASSERT_THAT(actual_value, matcher)` | The same as `EXPECT_THAT(actual_value, matcher)`, except that it generates a **fatal** failure. |
|
||||||
|
|
||||||
|
{: .callout .warning}
|
||||||
|
**WARNING:** Equality matching via `EXPECT_THAT(actual_value, expected_value)`
|
||||||
|
is supported, however note that implicit conversions can cause surprising
|
||||||
|
results. For example, `EXPECT_THAT(some_bool, "some string")` will compile and
|
||||||
|
may pass unintentionally.
|
||||||
|
|
||||||
|
**BEST PRACTICE:** Prefer to make the comparison explicit via
|
||||||
|
`EXPECT_THAT(actual_value, Eq(expected_value))` or `EXPECT_EQ(actual_value,
|
||||||
|
expected_value)`.
|
||||||
|
|
||||||
|
Built-in matchers (where `argument` is the function argument, e.g.
|
||||||
|
`actual_value` in the example above, or when used in the context of
|
||||||
|
`EXPECT_CALL(mock_object, method(matchers))`, the arguments of `method`) are
|
||||||
|
divided into several categories. All matchers are defined in the `::testing`
|
||||||
|
namespace unless otherwise noted.
|
||||||
|
|
||||||
|
## Wildcard
|
||||||
|
|
||||||
|
Matcher | Description
|
||||||
|
:-------------------------- | :-----------------------------------------------
|
||||||
|
`_` | `argument` can be any value of the correct type.
|
||||||
|
`A<type>()` or `An<type>()` | `argument` can be any value of type `type`.
|
||||||
|
|
||||||
|
## Generic Comparison
|
||||||
|
|
||||||
|
| Matcher | Description |
|
||||||
|
| :--------------------- | :-------------------------------------------------- |
|
||||||
|
| `Eq(value)` or `value` | `argument == value` |
|
||||||
|
| `Ge(value)` | `argument >= value` |
|
||||||
|
| `Gt(value)` | `argument > value` |
|
||||||
|
| `Le(value)` | `argument <= value` |
|
||||||
|
| `Lt(value)` | `argument < value` |
|
||||||
|
| `Ne(value)` | `argument != value` |
|
||||||
|
| `IsFalse()` | `argument` evaluates to `false` in a Boolean context. |
|
||||||
|
| `IsTrue()` | `argument` evaluates to `true` in a Boolean context. |
|
||||||
|
| `IsNull()` | `argument` is a `NULL` pointer (raw or smart). |
|
||||||
|
| `NotNull()` | `argument` is a non-null pointer (raw or smart). |
|
||||||
|
| `Optional(m)` | `argument` is `optional<>` that contains a value matching `m`. (For testing whether an `optional<>` is set, check for equality with `nullopt`. You may need to use `Eq(nullopt)` if the inner type doesn't have `==`.)|
|
||||||
|
| `VariantWith<T>(m)` | `argument` is `variant<>` that holds the alternative of type T with a value matching `m`. |
|
||||||
|
| `Ref(variable)` | `argument` is a reference to `variable`. |
|
||||||
|
| `TypedEq<type>(value)` | `argument` has type `type` and is equal to `value`. You may need to use this instead of `Eq(value)` when the mock function is overloaded. |
|
||||||
|
|
||||||
|
Except `Ref()`, these matchers make a *copy* of `value` in case it's modified or
|
||||||
|
destructed later. If the compiler complains that `value` doesn't have a public
|
||||||
|
copy constructor, try wrap it in `std::ref()`, e.g.
|
||||||
|
`Eq(std::ref(non_copyable_value))`. If you do that, make sure
|
||||||
|
`non_copyable_value` is not changed afterwards, or the meaning of your matcher
|
||||||
|
will be changed.
|
||||||
|
|
||||||
|
`IsTrue` and `IsFalse` are useful when you need to use a matcher, or for types
|
||||||
|
that can be explicitly converted to Boolean, but are not implicitly converted to
|
||||||
|
Boolean. In other cases, you can use the basic
|
||||||
|
[`EXPECT_TRUE` and `EXPECT_FALSE`](assertions.md#boolean) assertions.
|
||||||
|
|
||||||
|
## Floating-Point Matchers {#FpMatchers}
|
||||||
|
|
||||||
|
| Matcher | Description |
|
||||||
|
| :------------------------------- | :--------------------------------- |
|
||||||
|
| `DoubleEq(a_double)` | `argument` is a `double` value approximately equal to `a_double`, treating two NaNs as unequal. |
|
||||||
|
| `FloatEq(a_float)` | `argument` is a `float` value approximately equal to `a_float`, treating two NaNs as unequal. |
|
||||||
|
| `NanSensitiveDoubleEq(a_double)` | `argument` is a `double` value approximately equal to `a_double`, treating two NaNs as equal. |
|
||||||
|
| `NanSensitiveFloatEq(a_float)` | `argument` is a `float` value approximately equal to `a_float`, treating two NaNs as equal. |
|
||||||
|
| `IsNan()` | `argument` is any floating-point type with a NaN value. |
|
||||||
|
|
||||||
|
The above matchers use ULP-based comparison (the same as used in googletest).
|
||||||
|
They automatically pick a reasonable error bound based on the absolute value of
|
||||||
|
the expected value. `DoubleEq()` and `FloatEq()` conform to the IEEE standard,
|
||||||
|
which requires comparing two NaNs for equality to return false. The
|
||||||
|
`NanSensitive*` version instead treats two NaNs as equal, which is often what a
|
||||||
|
user wants.
|
||||||
|
|
||||||
|
| Matcher | Description |
|
||||||
|
| :------------------------------------------------ | :----------------------- |
|
||||||
|
| `DoubleNear(a_double, max_abs_error)` | `argument` is a `double` value close to `a_double` (absolute error <= `max_abs_error`), treating two NaNs as unequal. |
|
||||||
|
| `FloatNear(a_float, max_abs_error)` | `argument` is a `float` value close to `a_float` (absolute error <= `max_abs_error`), treating two NaNs as unequal. |
|
||||||
|
| `NanSensitiveDoubleNear(a_double, max_abs_error)` | `argument` is a `double` value close to `a_double` (absolute error <= `max_abs_error`), treating two NaNs as equal. |
|
||||||
|
| `NanSensitiveFloatNear(a_float, max_abs_error)` | `argument` is a `float` value close to `a_float` (absolute error <= `max_abs_error`), treating two NaNs as equal. |
|
||||||
|
|
||||||
|
## String Matchers
|
||||||
|
|
||||||
|
The `argument` can be either a C string or a C++ string object:
|
||||||
|
|
||||||
|
| Matcher | Description |
|
||||||
|
| :---------------------- | :------------------------------------------------- |
|
||||||
|
| `ContainsRegex(string)` | `argument` matches the given regular expression. |
|
||||||
|
| `EndsWith(suffix)` | `argument` ends with string `suffix`. |
|
||||||
|
| `HasSubstr(string)` | `argument` contains `string` as a sub-string. |
|
||||||
|
| `IsEmpty()` | `argument` is an empty string. |
|
||||||
|
| `MatchesRegex(string)` | `argument` matches the given regular expression with the match starting at the first character and ending at the last character. |
|
||||||
|
| `StartsWith(prefix)` | `argument` starts with string `prefix`. |
|
||||||
|
| `StrCaseEq(string)` | `argument` is equal to `string`, ignoring case. |
|
||||||
|
| `StrCaseNe(string)` | `argument` is not equal to `string`, ignoring case. |
|
||||||
|
| `StrEq(string)` | `argument` is equal to `string`. |
|
||||||
|
| `StrNe(string)` | `argument` is not equal to `string`. |
|
||||||
|
| `WhenBase64Unescaped(m)` | `argument` is a base-64 escaped string whose unescaped string matches `m`. The web-safe format from [RFC 4648](https://www.rfc-editor.org/rfc/rfc4648#section-5) is supported. |
|
||||||
|
|
||||||
|
`ContainsRegex()` and `MatchesRegex()` take ownership of the `RE` object. They
|
||||||
|
use the regular expression syntax defined
|
||||||
|
[here](../advanced.md#regular-expression-syntax). All of these matchers, except
|
||||||
|
`ContainsRegex()` and `MatchesRegex()` work for wide strings as well.
|
||||||
|
|
||||||
|
## Container Matchers
|
||||||
|
|
||||||
|
Most STL-style containers support `==`, so you can use `Eq(expected_container)`
|
||||||
|
or simply `expected_container` to match a container exactly. If you want to
|
||||||
|
write the elements in-line, match them more flexibly, or get more informative
|
||||||
|
messages, you can use:
|
||||||
|
|
||||||
|
| Matcher | Description |
|
||||||
|
| :---------------------------------------- | :------------------------------- |
|
||||||
|
| `BeginEndDistanceIs(m)` | `argument` is a container whose `begin()` and `end()` iterators are separated by a number of increments matching `m`. E.g. `BeginEndDistanceIs(2)` or `BeginEndDistanceIs(Lt(2))`. For containers that define a `size()` method, `SizeIs(m)` may be more efficient. |
|
||||||
|
| `ContainerEq(container)` | The same as `Eq(container)` except that the failure message also includes which elements are in one container but not the other. |
|
||||||
|
| `Contains(e)` | `argument` contains an element that matches `e`, which can be either a value or a matcher. |
|
||||||
|
| `Contains(e).Times(n)` | `argument` contains elements that match `e`, which can be either a value or a matcher, and the number of matches is `n`, which can be either a value or a matcher. Unlike the plain `Contains` and `Each` this allows to check for arbitrary occurrences including testing for absence with `Contains(e).Times(0)`. |
|
||||||
|
| `Each(e)` | `argument` is a container where *every* element matches `e`, which can be either a value or a matcher. |
|
||||||
|
| `ElementsAre(e0, e1, ..., en)` | `argument` has `n + 1` elements, where the *i*-th element matches `ei`, which can be a value or a matcher. |
|
||||||
|
| `ElementsAreArray({e0, e1, ..., en})`, `ElementsAreArray(a_container)`, `ElementsAreArray(begin, end)`, `ElementsAreArray(array)`, or `ElementsAreArray(array, count)` | The same as `ElementsAre()` except that the expected element values/matchers come from an initializer list, STL-style container, iterator range, or C-style array. |
|
||||||
|
| `IsEmpty()` | `argument` is an empty container (`container.empty()`). |
|
||||||
|
| `IsSubsetOf({e0, e1, ..., en})`, `IsSubsetOf(a_container)`, `IsSubsetOf(begin, end)`, `IsSubsetOf(array)`, or `IsSubsetOf(array, count)` | `argument` matches `UnorderedElementsAre(x0, x1, ..., xk)` for some subset `{x0, x1, ..., xk}` of the expected matchers. |
|
||||||
|
| `IsSupersetOf({e0, e1, ..., en})`, `IsSupersetOf(a_container)`, `IsSupersetOf(begin, end)`, `IsSupersetOf(array)`, or `IsSupersetOf(array, count)` | Some subset of `argument` matches `UnorderedElementsAre(`expected matchers`)`. |
|
||||||
|
| `Pointwise(m, container)`, `Pointwise(m, {e0, e1, ..., en})` | `argument` contains the same number of elements as in `container`, and for all i, (the i-th element in `argument`, the i-th element in `container`) match `m`, which is a matcher on 2-tuples. E.g. `Pointwise(Le(), upper_bounds)` verifies that each element in `argument` doesn't exceed the corresponding element in `upper_bounds`. See more detail below. |
|
||||||
|
| `SizeIs(m)` | `argument` is a container whose size matches `m`. E.g. `SizeIs(2)` or `SizeIs(Lt(2))`. |
|
||||||
|
| `UnorderedElementsAre(e0, e1, ..., en)` | `argument` has `n + 1` elements, and under *some* permutation of the elements, each element matches an `ei` (for a different `i`), which can be a value or a matcher. |
|
||||||
|
| `UnorderedElementsAreArray({e0, e1, ..., en})`, `UnorderedElementsAreArray(a_container)`, `UnorderedElementsAreArray(begin, end)`, `UnorderedElementsAreArray(array)`, or `UnorderedElementsAreArray(array, count)` | The same as `UnorderedElementsAre()` except that the expected element values/matchers come from an initializer list, STL-style container, iterator range, or C-style array. |
|
||||||
|
| `UnorderedPointwise(m, container)`, `UnorderedPointwise(m, {e0, e1, ..., en})` | Like `Pointwise(m, container)`, but ignores the order of elements. |
|
||||||
|
| `WhenSorted(m)` | When `argument` is sorted using the `<` operator, it matches container matcher `m`. E.g. `WhenSorted(ElementsAre(1, 2, 3))` verifies that `argument` contains elements 1, 2, and 3, ignoring order. |
|
||||||
|
| `WhenSortedBy(comparator, m)` | The same as `WhenSorted(m)`, except that the given comparator instead of `<` is used to sort `argument`. E.g. `WhenSortedBy(std::greater(), ElementsAre(3, 2, 1))`. |
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
|
||||||
|
* These matchers can also match:
|
||||||
|
1. a native array passed by reference (e.g. in `Foo(const int (&a)[5])`),
|
||||||
|
and
|
||||||
|
2. an array passed as a pointer and a count (e.g. in `Bar(const T* buffer,
|
||||||
|
int len)` -- see [Multi-argument Matchers](#MultiArgMatchers)).
|
||||||
|
* The array being matched may be multi-dimensional (i.e. its elements can be
|
||||||
|
arrays).
|
||||||
|
* `m` in `Pointwise(m, ...)` and `UnorderedPointwise(m, ...)` should be a
|
||||||
|
matcher for `::std::tuple<T, U>` where `T` and `U` are the element type of
|
||||||
|
the actual container and the expected container, respectively. For example,
|
||||||
|
to compare two `Foo` containers where `Foo` doesn't support `operator==`,
|
||||||
|
one might write:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
MATCHER(FooEq, "") {
|
||||||
|
return std::get<0>(arg).Equals(std::get<1>(arg));
|
||||||
|
}
|
||||||
|
...
|
||||||
|
EXPECT_THAT(actual_foos, Pointwise(FooEq(), expected_foos));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Member Matchers
|
||||||
|
|
||||||
|
| Matcher | Description |
|
||||||
|
| :------------------------------ | :----------------------------------------- |
|
||||||
|
| `Field(&class::field, m)` | `argument.field` (or `argument->field` when `argument` is a plain pointer) matches matcher `m`, where `argument` is an object of type _class_. |
|
||||||
|
| `Field(field_name, &class::field, m)` | The same as the two-parameter version, but provides a better error message. |
|
||||||
|
| `Key(e)` | `argument.first` matches `e`, which can be either a value or a matcher. E.g. `Contains(Key(Le(5)))` can verify that a `map` contains a key `<= 5`. |
|
||||||
|
| `Pair(m1, m2)` | `argument` is an `std::pair` whose `first` field matches `m1` and `second` field matches `m2`. |
|
||||||
|
| `FieldsAre(m...)` | `argument` is a compatible object where each field matches piecewise with the matchers `m...`. A compatible object is any that supports the `std::tuple_size<Obj>`+`get<I>(obj)` protocol. In C++17 and up this also supports types compatible with structured bindings, like aggregates. |
|
||||||
|
| `Property(&class::property, m)` | `argument.property()` (or `argument->property()` when `argument` is a plain pointer) matches matcher `m`, where `argument` is an object of type _class_. The method `property()` must take no argument and be declared as `const`. |
|
||||||
|
| `Property(property_name, &class::property, m)` | The same as the two-parameter version, but provides a better error message.
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
|
||||||
|
* You can use `FieldsAre()` to match any type that supports structured
|
||||||
|
bindings, such as `std::tuple`, `std::pair`, `std::array`, and aggregate
|
||||||
|
types. For example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::tuple<int, std::string> my_tuple{7, "hello world"};
|
||||||
|
EXPECT_THAT(my_tuple, FieldsAre(Ge(0), HasSubstr("hello")));
|
||||||
|
|
||||||
|
struct MyStruct {
|
||||||
|
int value = 42;
|
||||||
|
std::string greeting = "aloha";
|
||||||
|
};
|
||||||
|
MyStruct s;
|
||||||
|
EXPECT_THAT(s, FieldsAre(42, "aloha"));
|
||||||
|
```
|
||||||
|
|
||||||
|
* Don't use `Property()` against member functions that you do not own, because
|
||||||
|
taking addresses of functions is fragile and generally not part of the
|
||||||
|
contract of the function.
|
||||||
|
|
||||||
|
## Matching the Result of a Function, Functor, or Callback
|
||||||
|
|
||||||
|
| Matcher | Description |
|
||||||
|
| :--------------- | :------------------------------------------------ |
|
||||||
|
| `ResultOf(f, m)` | `f(argument)` matches matcher `m`, where `f` is a function or functor. |
|
||||||
|
| `ResultOf(result_description, f, m)` | The same as the two-parameter version, but provides a better error message.
|
||||||
|
|
||||||
|
## Pointer Matchers
|
||||||
|
|
||||||
|
| Matcher | Description |
|
||||||
|
| :------------------------ | :---------------------------------------------- |
|
||||||
|
| `Address(m)` | the result of `std::addressof(argument)` matches `m`. |
|
||||||
|
| `Pointee(m)` | `argument` (either a smart pointer or a raw pointer) points to a value that matches matcher `m`. |
|
||||||
|
| `Pointer(m)` | `argument` (either a smart pointer or a raw pointer) contains a pointer that matches `m`. `m` will match against the raw pointer regardless of the type of `argument`. |
|
||||||
|
| `WhenDynamicCastTo<T>(m)` | when `argument` is passed through `dynamic_cast<T>()`, it matches matcher `m`. |
|
||||||
|
|
||||||
|
## Multi-argument Matchers {#MultiArgMatchers}
|
||||||
|
|
||||||
|
Technically, all matchers match a *single* value. A "multi-argument" matcher is
|
||||||
|
just one that matches a *tuple*. The following matchers can be used to match a
|
||||||
|
tuple `(x, y)`:
|
||||||
|
|
||||||
|
Matcher | Description
|
||||||
|
:------ | :----------
|
||||||
|
`Eq()` | `x == y`
|
||||||
|
`Ge()` | `x >= y`
|
||||||
|
`Gt()` | `x > y`
|
||||||
|
`Le()` | `x <= y`
|
||||||
|
`Lt()` | `x < y`
|
||||||
|
`Ne()` | `x != y`
|
||||||
|
|
||||||
|
You can use the following selectors to pick a subset of the arguments (or
|
||||||
|
reorder them) to participate in the matching:
|
||||||
|
|
||||||
|
| Matcher | Description |
|
||||||
|
| :------------------------- | :---------------------------------------------- |
|
||||||
|
| `AllArgs(m)` | Equivalent to `m`. Useful as syntactic sugar in `.With(AllArgs(m))`. |
|
||||||
|
| `Args<N1, N2, ..., Nk>(m)` | The tuple of the `k` selected (using 0-based indices) arguments matches `m`, e.g. `Args<1, 2>(Eq())`. |
|
||||||
|
|
||||||
|
## Composite Matchers
|
||||||
|
|
||||||
|
You can make a matcher from one or more other matchers:
|
||||||
|
|
||||||
|
| Matcher | Description |
|
||||||
|
| :------------------------------- | :-------------------------------------- |
|
||||||
|
| `AllOf(m1, m2, ..., mn)` | `argument` matches all of the matchers `m1` to `mn`. |
|
||||||
|
| `AllOfArray({m0, m1, ..., mn})`, `AllOfArray(a_container)`, `AllOfArray(begin, end)`, `AllOfArray(array)`, or `AllOfArray(array, count)` | The same as `AllOf()` except that the matchers come from an initializer list, STL-style container, iterator range, or C-style array. |
|
||||||
|
| `AnyOf(m1, m2, ..., mn)` | `argument` matches at least one of the matchers `m1` to `mn`. |
|
||||||
|
| `AnyOfArray({m0, m1, ..., mn})`, `AnyOfArray(a_container)`, `AnyOfArray(begin, end)`, `AnyOfArray(array)`, or `AnyOfArray(array, count)` | The same as `AnyOf()` except that the matchers come from an initializer list, STL-style container, iterator range, or C-style array. |
|
||||||
|
| `Not(m)` | `argument` doesn't match matcher `m`. |
|
||||||
|
| `Conditional(cond, m1, m2)` | Matches matcher `m1` if `cond` evaluates to true, else matches `m2`.|
|
||||||
|
|
||||||
|
## Adapters for Matchers
|
||||||
|
|
||||||
|
| Matcher | Description |
|
||||||
|
| :---------------------- | :------------------------------------ |
|
||||||
|
| `MatcherCast<T>(m)` | casts matcher `m` to type `Matcher<T>`. |
|
||||||
|
| `SafeMatcherCast<T>(m)` | [safely casts](../gmock_cook_book.md#SafeMatcherCast) matcher `m` to type `Matcher<T>`. |
|
||||||
|
| `Truly(predicate)` | `predicate(argument)` returns something considered by C++ to be true, where `predicate` is a function or functor. |
|
||||||
|
|
||||||
|
`AddressSatisfies(callback)` and `Truly(callback)` take ownership of `callback`,
|
||||||
|
which must be a permanent callback.
|
||||||
|
|
||||||
|
## Using Matchers as Predicates {#MatchersAsPredicatesCheat}
|
||||||
|
|
||||||
|
| Matcher | Description |
|
||||||
|
| :---------------------------- | :------------------------------------------ |
|
||||||
|
| `Matches(m)(value)` | evaluates to `true` if `value` matches `m`. You can use `Matches(m)` alone as a unary functor. |
|
||||||
|
| `ExplainMatchResult(m, value, result_listener)` | evaluates to `true` if `value` matches `m`, explaining the result to `result_listener`. |
|
||||||
|
| `Value(value, m)` | evaluates to `true` if `value` matches `m`. |
|
||||||
|
|
||||||
|
## Defining Matchers
|
||||||
|
|
||||||
|
| Macro | Description |
|
||||||
|
| :----------------------------------- | :------------------------------------ |
|
||||||
|
| `MATCHER(IsEven, "") { return (arg % 2) == 0; }` | Defines a matcher `IsEven()` to match an even number. |
|
||||||
|
| `MATCHER_P(IsDivisibleBy, n, "") { *result_listener << "where the remainder is " << (arg % n); return (arg % n) == 0; }` | Defines a matcher `IsDivisibleBy(n)` to match a number divisible by `n`. |
|
||||||
|
| `MATCHER_P2(IsBetween, a, b, absl::StrCat(negation ? "isn't" : "is", " between ", PrintToString(a), " and ", PrintToString(b))) { return a <= arg && arg <= b; }` | Defines a matcher `IsBetween(a, b)` to match a value in the range [`a`, `b`]. |
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
|
||||||
|
1. The `MATCHER*` macros cannot be used inside a function or class.
|
||||||
|
2. The matcher body must be *purely functional* (i.e. it cannot have any side
|
||||||
|
effect, and the result must not depend on anything other than the value
|
||||||
|
being matched and the matcher parameters).
|
||||||
|
3. You can use `PrintToString(x)` to convert a value `x` of any type to a
|
||||||
|
string.
|
||||||
|
4. You can use `ExplainMatchResult()` in a custom matcher to wrap another
|
||||||
|
matcher, for example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
MATCHER_P(NestedPropertyMatches, matcher, "") {
|
||||||
|
return ExplainMatchResult(matcher, arg.nested().property(), result_listener);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
5. You can use `DescribeMatcher<>` to describe another matcher. For example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
MATCHER_P(XAndYThat, matcher,
|
||||||
|
"X that " + DescribeMatcher<int>(matcher, negation) +
|
||||||
|
(negation ? " or" : " and") + " Y that " +
|
||||||
|
DescribeMatcher<double>(matcher, negation)) {
|
||||||
|
return ExplainMatchResult(matcher, arg.x(), result_listener) &&
|
||||||
|
ExplainMatchResult(matcher, arg.y(), result_listener);
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,589 @@
|
||||||
|
# Mocking Reference
|
||||||
|
|
||||||
|
This page lists the facilities provided by GoogleTest for creating and working
|
||||||
|
with mock objects. To use them, include the header
|
||||||
|
`gmock/gmock.h`.
|
||||||
|
|
||||||
|
## Macros {#macros}
|
||||||
|
|
||||||
|
GoogleTest defines the following macros for working with mocks.
|
||||||
|
|
||||||
|
### MOCK_METHOD {#MOCK_METHOD}
|
||||||
|
|
||||||
|
`MOCK_METHOD(`*`return_type`*`,`*`method_name`*`, (`*`args...`*`));` \
|
||||||
|
`MOCK_METHOD(`*`return_type`*`,`*`method_name`*`, (`*`args...`*`),
|
||||||
|
(`*`specs...`*`));`
|
||||||
|
|
||||||
|
Defines a mock method *`method_name`* with arguments `(`*`args...`*`)` and
|
||||||
|
return type *`return_type`* within a mock class.
|
||||||
|
|
||||||
|
The parameters of `MOCK_METHOD` mirror the method declaration. The optional
|
||||||
|
fourth parameter *`specs...`* is a comma-separated list of qualifiers. The
|
||||||
|
following qualifiers are accepted:
|
||||||
|
|
||||||
|
| Qualifier | Meaning |
|
||||||
|
| -------------------------- | -------------------------------------------- |
|
||||||
|
| `const` | Makes the mocked method a `const` method. Required if overriding a `const` method. |
|
||||||
|
| `override` | Marks the method with `override`. Recommended if overriding a `virtual` method. |
|
||||||
|
| `noexcept` | Marks the method with `noexcept`. Required if overriding a `noexcept` method. |
|
||||||
|
| `Calltype(`*`calltype`*`)` | Sets the call type for the method, for example `Calltype(STDMETHODCALLTYPE)`. Useful on Windows. |
|
||||||
|
| `ref(`*`qualifier`*`)` | Marks the method with the given reference qualifier, for example `ref(&)` or `ref(&&)`. Required if overriding a method that has a reference qualifier. |
|
||||||
|
|
||||||
|
Note that commas in arguments prevent `MOCK_METHOD` from parsing the arguments
|
||||||
|
correctly if they are not appropriately surrounded by parentheses. See the
|
||||||
|
following example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class MyMock {
|
||||||
|
public:
|
||||||
|
// The following 2 lines will not compile due to commas in the arguments:
|
||||||
|
MOCK_METHOD(std::pair<bool, int>, GetPair, ()); // Error!
|
||||||
|
MOCK_METHOD(bool, CheckMap, (std::map<int, double>, bool)); // Error!
|
||||||
|
|
||||||
|
// One solution - wrap arguments that contain commas in parentheses:
|
||||||
|
MOCK_METHOD((std::pair<bool, int>), GetPair, ());
|
||||||
|
MOCK_METHOD(bool, CheckMap, ((std::map<int, double>), bool));
|
||||||
|
|
||||||
|
// Another solution - use type aliases:
|
||||||
|
using BoolAndInt = std::pair<bool, int>;
|
||||||
|
MOCK_METHOD(BoolAndInt, GetPair, ());
|
||||||
|
using MapIntDouble = std::map<int, double>;
|
||||||
|
MOCK_METHOD(bool, CheckMap, (MapIntDouble, bool));
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
`MOCK_METHOD` must be used in the `public:` section of a mock class definition,
|
||||||
|
regardless of whether the method being mocked is `public`, `protected`, or
|
||||||
|
`private` in the base class.
|
||||||
|
|
||||||
|
### EXPECT_CALL {#EXPECT_CALL}
|
||||||
|
|
||||||
|
`EXPECT_CALL(`*`mock_object`*`,`*`method_name`*`(`*`matchers...`*`))`
|
||||||
|
|
||||||
|
Creates an [expectation](../gmock_for_dummies.md#setting-expectations) that the
|
||||||
|
method *`method_name`* of the object *`mock_object`* is called with arguments
|
||||||
|
that match the given matchers *`matchers...`*. `EXPECT_CALL` must precede any
|
||||||
|
code that exercises the mock object.
|
||||||
|
|
||||||
|
The parameter *`matchers...`* is a comma-separated list of
|
||||||
|
[matchers](../gmock_for_dummies.md#matchers-what-arguments-do-we-expect) that
|
||||||
|
correspond to each argument of the method *`method_name`*. The expectation will
|
||||||
|
apply only to calls of *`method_name`* whose arguments match all of the
|
||||||
|
matchers. If `(`*`matchers...`*`)` is omitted, the expectation behaves as if
|
||||||
|
each argument's matcher were a [wildcard matcher (`_`)](matchers.md#wildcard).
|
||||||
|
See the [Matchers Reference](matchers.md) for a list of all built-in matchers.
|
||||||
|
|
||||||
|
The following chainable clauses can be used to modify the expectation, and they
|
||||||
|
must be used in the following order:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
EXPECT_CALL(mock_object, method_name(matchers...))
|
||||||
|
.With(multi_argument_matcher) // Can be used at most once
|
||||||
|
.Times(cardinality) // Can be used at most once
|
||||||
|
.InSequence(sequences...) // Can be used any number of times
|
||||||
|
.After(expectations...) // Can be used any number of times
|
||||||
|
.WillOnce(action) // Can be used any number of times
|
||||||
|
.WillRepeatedly(action) // Can be used at most once
|
||||||
|
.RetiresOnSaturation(); // Can be used at most once
|
||||||
|
```
|
||||||
|
|
||||||
|
See details for each modifier clause below.
|
||||||
|
|
||||||
|
#### With {#EXPECT_CALL.With}
|
||||||
|
|
||||||
|
`.With(`*`multi_argument_matcher`*`)`
|
||||||
|
|
||||||
|
Restricts the expectation to apply only to mock function calls whose arguments
|
||||||
|
as a whole match the multi-argument matcher *`multi_argument_matcher`*.
|
||||||
|
|
||||||
|
GoogleTest passes all of the arguments as one tuple into the matcher. The
|
||||||
|
parameter *`multi_argument_matcher`* must thus be a matcher of type
|
||||||
|
`Matcher<std::tuple<A1, ..., An>>`, where `A1, ..., An` are the types of the
|
||||||
|
function arguments.
|
||||||
|
|
||||||
|
For example, the following code sets the expectation that
|
||||||
|
`my_mock.SetPosition()` is called with any two arguments, the first argument
|
||||||
|
being less than the second:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::_;
|
||||||
|
using ::testing::Lt;
|
||||||
|
...
|
||||||
|
EXPECT_CALL(my_mock, SetPosition(_, _))
|
||||||
|
.With(Lt());
|
||||||
|
```
|
||||||
|
|
||||||
|
GoogleTest provides some built-in matchers for 2-tuples, including the `Lt()`
|
||||||
|
matcher above. See [Multi-argument Matchers](matchers.md#MultiArgMatchers).
|
||||||
|
|
||||||
|
The `With` clause can be used at most once on an expectation and must be the
|
||||||
|
first clause.
|
||||||
|
|
||||||
|
#### Times {#EXPECT_CALL.Times}
|
||||||
|
|
||||||
|
`.Times(`*`cardinality`*`)`
|
||||||
|
|
||||||
|
Specifies how many times the mock function call is expected.
|
||||||
|
|
||||||
|
The parameter *`cardinality`* represents the number of expected calls and can be
|
||||||
|
one of the following, all defined in the `::testing` namespace:
|
||||||
|
|
||||||
|
| Cardinality | Meaning |
|
||||||
|
| ------------------- | --------------------------------------------------- |
|
||||||
|
| `AnyNumber()` | The function can be called any number of times. |
|
||||||
|
| `AtLeast(n)` | The function call is expected at least *n* times. |
|
||||||
|
| `AtMost(n)` | The function call is expected at most *n* times. |
|
||||||
|
| `Between(m, n)` | The function call is expected between *m* and *n* times, inclusive. |
|
||||||
|
| `Exactly(n)` or `n` | The function call is expected exactly *n* times. If *n* is 0, the call should never happen. |
|
||||||
|
|
||||||
|
If the `Times` clause is omitted, GoogleTest infers the cardinality as follows:
|
||||||
|
|
||||||
|
* If neither [`WillOnce`](#EXPECT_CALL.WillOnce) nor
|
||||||
|
[`WillRepeatedly`](#EXPECT_CALL.WillRepeatedly) are specified, the inferred
|
||||||
|
cardinality is `Times(1)`.
|
||||||
|
* If there are *n* `WillOnce` clauses and no `WillRepeatedly` clause, where
|
||||||
|
*n* >= 1, the inferred cardinality is `Times(n)`.
|
||||||
|
* If there are *n* `WillOnce` clauses and one `WillRepeatedly` clause, where
|
||||||
|
*n* >= 0, the inferred cardinality is `Times(AtLeast(n))`.
|
||||||
|
|
||||||
|
The `Times` clause can be used at most once on an expectation.
|
||||||
|
|
||||||
|
#### InSequence {#EXPECT_CALL.InSequence}
|
||||||
|
|
||||||
|
`.InSequence(`*`sequences...`*`)`
|
||||||
|
|
||||||
|
Specifies that the mock function call is expected in a certain sequence.
|
||||||
|
|
||||||
|
The parameter *`sequences...`* is any number of [`Sequence`](#Sequence) objects.
|
||||||
|
Expected calls assigned to the same sequence are expected to occur in the order
|
||||||
|
the expectations are declared.
|
||||||
|
|
||||||
|
For example, the following code sets the expectation that the `Reset()` method
|
||||||
|
of `my_mock` is called before both `GetSize()` and `Describe()`, and `GetSize()`
|
||||||
|
and `Describe()` can occur in any order relative to each other:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Sequence;
|
||||||
|
Sequence s1, s2;
|
||||||
|
...
|
||||||
|
EXPECT_CALL(my_mock, Reset())
|
||||||
|
.InSequence(s1, s2);
|
||||||
|
EXPECT_CALL(my_mock, GetSize())
|
||||||
|
.InSequence(s1);
|
||||||
|
EXPECT_CALL(my_mock, Describe())
|
||||||
|
.InSequence(s2);
|
||||||
|
```
|
||||||
|
|
||||||
|
The `InSequence` clause can be used any number of times on an expectation.
|
||||||
|
|
||||||
|
See also the [`InSequence` class](#InSequence).
|
||||||
|
|
||||||
|
#### After {#EXPECT_CALL.After}
|
||||||
|
|
||||||
|
`.After(`*`expectations...`*`)`
|
||||||
|
|
||||||
|
Specifies that the mock function call is expected to occur after one or more
|
||||||
|
other calls.
|
||||||
|
|
||||||
|
The parameter *`expectations...`* can be up to five
|
||||||
|
[`Expectation`](#Expectation) or [`ExpectationSet`](#ExpectationSet) objects.
|
||||||
|
The mock function call is expected to occur after all of the given expectations.
|
||||||
|
|
||||||
|
For example, the following code sets the expectation that the `Describe()`
|
||||||
|
method of `my_mock` is called only after both `InitX()` and `InitY()` have been
|
||||||
|
called.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Expectation;
|
||||||
|
...
|
||||||
|
Expectation init_x = EXPECT_CALL(my_mock, InitX());
|
||||||
|
Expectation init_y = EXPECT_CALL(my_mock, InitY());
|
||||||
|
EXPECT_CALL(my_mock, Describe())
|
||||||
|
.After(init_x, init_y);
|
||||||
|
```
|
||||||
|
|
||||||
|
The `ExpectationSet` object is helpful when the number of prerequisites for an
|
||||||
|
expectation is large or variable, for example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::ExpectationSet;
|
||||||
|
...
|
||||||
|
ExpectationSet all_inits;
|
||||||
|
// Collect all expectations of InitElement() calls
|
||||||
|
for (int i = 0; i < element_count; i++) {
|
||||||
|
all_inits += EXPECT_CALL(my_mock, InitElement(i));
|
||||||
|
}
|
||||||
|
EXPECT_CALL(my_mock, Describe())
|
||||||
|
.After(all_inits); // Expect Describe() call after all InitElement() calls
|
||||||
|
```
|
||||||
|
|
||||||
|
The `After` clause can be used any number of times on an expectation.
|
||||||
|
|
||||||
|
#### WillOnce {#EXPECT_CALL.WillOnce}
|
||||||
|
|
||||||
|
`.WillOnce(`*`action`*`)`
|
||||||
|
|
||||||
|
Specifies the mock function's actual behavior when invoked, for a single
|
||||||
|
matching function call.
|
||||||
|
|
||||||
|
The parameter *`action`* represents the
|
||||||
|
[action](../gmock_for_dummies.md#actions-what-should-it-do) that the function
|
||||||
|
call will perform. See the [Actions Reference](actions.md) for a list of
|
||||||
|
built-in actions.
|
||||||
|
|
||||||
|
The use of `WillOnce` implicitly sets a cardinality on the expectation when
|
||||||
|
`Times` is not specified. See [`Times`](#EXPECT_CALL.Times).
|
||||||
|
|
||||||
|
Each matching function call will perform the next action in the order declared.
|
||||||
|
For example, the following code specifies that `my_mock.GetNumber()` is expected
|
||||||
|
to be called exactly 3 times and will return `1`, `2`, and `3` respectively on
|
||||||
|
the first, second, and third calls:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Return;
|
||||||
|
...
|
||||||
|
EXPECT_CALL(my_mock, GetNumber())
|
||||||
|
.WillOnce(Return(1))
|
||||||
|
.WillOnce(Return(2))
|
||||||
|
.WillOnce(Return(3));
|
||||||
|
```
|
||||||
|
|
||||||
|
The `WillOnce` clause can be used any number of times on an expectation. Unlike
|
||||||
|
`WillRepeatedly`, the action fed to each `WillOnce` call will be called at most
|
||||||
|
once, so may be a move-only type and/or have an `&&`-qualified call operator.
|
||||||
|
|
||||||
|
#### WillRepeatedly {#EXPECT_CALL.WillRepeatedly}
|
||||||
|
|
||||||
|
`.WillRepeatedly(`*`action`*`)`
|
||||||
|
|
||||||
|
Specifies the mock function's actual behavior when invoked, for all subsequent
|
||||||
|
matching function calls. Takes effect after the actions specified in the
|
||||||
|
[`WillOnce`](#EXPECT_CALL.WillOnce) clauses, if any, have been performed.
|
||||||
|
|
||||||
|
The parameter *`action`* represents the
|
||||||
|
[action](../gmock_for_dummies.md#actions-what-should-it-do) that the function
|
||||||
|
call will perform. See the [Actions Reference](actions.md) for a list of
|
||||||
|
built-in actions.
|
||||||
|
|
||||||
|
The use of `WillRepeatedly` implicitly sets a cardinality on the expectation
|
||||||
|
when `Times` is not specified. See [`Times`](#EXPECT_CALL.Times).
|
||||||
|
|
||||||
|
If any `WillOnce` clauses have been specified, matching function calls will
|
||||||
|
perform those actions before the action specified by `WillRepeatedly`. See the
|
||||||
|
following example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Return;
|
||||||
|
...
|
||||||
|
EXPECT_CALL(my_mock, GetName())
|
||||||
|
.WillRepeatedly(Return("John Doe")); // Return "John Doe" on all calls
|
||||||
|
|
||||||
|
EXPECT_CALL(my_mock, GetNumber())
|
||||||
|
.WillOnce(Return(42)) // Return 42 on the first call
|
||||||
|
.WillRepeatedly(Return(7)); // Return 7 on all subsequent calls
|
||||||
|
```
|
||||||
|
|
||||||
|
The `WillRepeatedly` clause can be used at most once on an expectation.
|
||||||
|
|
||||||
|
#### RetiresOnSaturation {#EXPECT_CALL.RetiresOnSaturation}
|
||||||
|
|
||||||
|
`.RetiresOnSaturation()`
|
||||||
|
|
||||||
|
Indicates that the expectation will no longer be active after the expected
|
||||||
|
number of matching function calls has been reached.
|
||||||
|
|
||||||
|
The `RetiresOnSaturation` clause is only meaningful for expectations with an
|
||||||
|
upper-bounded cardinality. The expectation will *retire* (no longer match any
|
||||||
|
function calls) after it has been *saturated* (the upper bound has been
|
||||||
|
reached). See the following example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::_;
|
||||||
|
using ::testing::AnyNumber;
|
||||||
|
...
|
||||||
|
EXPECT_CALL(my_mock, SetNumber(_)) // Expectation 1
|
||||||
|
.Times(AnyNumber());
|
||||||
|
EXPECT_CALL(my_mock, SetNumber(7)) // Expectation 2
|
||||||
|
.Times(2)
|
||||||
|
.RetiresOnSaturation();
|
||||||
|
```
|
||||||
|
|
||||||
|
In the above example, the first two calls to `my_mock.SetNumber(7)` match
|
||||||
|
expectation 2, which then becomes inactive and no longer matches any calls. A
|
||||||
|
third call to `my_mock.SetNumber(7)` would then match expectation 1. Without
|
||||||
|
`RetiresOnSaturation()` on expectation 2, a third call to `my_mock.SetNumber(7)`
|
||||||
|
would match expectation 2 again, producing a failure since the limit of 2 calls
|
||||||
|
was exceeded.
|
||||||
|
|
||||||
|
The `RetiresOnSaturation` clause can be used at most once on an expectation and
|
||||||
|
must be the last clause.
|
||||||
|
|
||||||
|
### ON_CALL {#ON_CALL}
|
||||||
|
|
||||||
|
`ON_CALL(`*`mock_object`*`,`*`method_name`*`(`*`matchers...`*`))`
|
||||||
|
|
||||||
|
Defines what happens when the method *`method_name`* of the object
|
||||||
|
*`mock_object`* is called with arguments that match the given matchers
|
||||||
|
*`matchers...`*. Requires a modifier clause to specify the method's behavior.
|
||||||
|
*Does not* set any expectations that the method will be called.
|
||||||
|
|
||||||
|
The parameter *`matchers...`* is a comma-separated list of
|
||||||
|
[matchers](../gmock_for_dummies.md#matchers-what-arguments-do-we-expect) that
|
||||||
|
correspond to each argument of the method *`method_name`*. The `ON_CALL`
|
||||||
|
specification will apply only to calls of *`method_name`* whose arguments match
|
||||||
|
all of the matchers. If `(`*`matchers...`*`)` is omitted, the behavior is as if
|
||||||
|
each argument's matcher were a [wildcard matcher (`_`)](matchers.md#wildcard).
|
||||||
|
See the [Matchers Reference](matchers.md) for a list of all built-in matchers.
|
||||||
|
|
||||||
|
The following chainable clauses can be used to set the method's behavior, and
|
||||||
|
they must be used in the following order:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
ON_CALL(mock_object, method_name(matchers...))
|
||||||
|
.With(multi_argument_matcher) // Can be used at most once
|
||||||
|
.WillByDefault(action); // Required
|
||||||
|
```
|
||||||
|
|
||||||
|
See details for each modifier clause below.
|
||||||
|
|
||||||
|
#### With {#ON_CALL.With}
|
||||||
|
|
||||||
|
`.With(`*`multi_argument_matcher`*`)`
|
||||||
|
|
||||||
|
Restricts the specification to only mock function calls whose arguments as a
|
||||||
|
whole match the multi-argument matcher *`multi_argument_matcher`*.
|
||||||
|
|
||||||
|
GoogleTest passes all of the arguments as one tuple into the matcher. The
|
||||||
|
parameter *`multi_argument_matcher`* must thus be a matcher of type
|
||||||
|
`Matcher<std::tuple<A1, ..., An>>`, where `A1, ..., An` are the types of the
|
||||||
|
function arguments.
|
||||||
|
|
||||||
|
For example, the following code sets the default behavior when
|
||||||
|
`my_mock.SetPosition()` is called with any two arguments, the first argument
|
||||||
|
being less than the second:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::_;
|
||||||
|
using ::testing::Lt;
|
||||||
|
using ::testing::Return;
|
||||||
|
...
|
||||||
|
ON_CALL(my_mock, SetPosition(_, _))
|
||||||
|
.With(Lt())
|
||||||
|
.WillByDefault(Return(true));
|
||||||
|
```
|
||||||
|
|
||||||
|
GoogleTest provides some built-in matchers for 2-tuples, including the `Lt()`
|
||||||
|
matcher above. See [Multi-argument Matchers](matchers.md#MultiArgMatchers).
|
||||||
|
|
||||||
|
The `With` clause can be used at most once with each `ON_CALL` statement.
|
||||||
|
|
||||||
|
#### WillByDefault {#ON_CALL.WillByDefault}
|
||||||
|
|
||||||
|
`.WillByDefault(`*`action`*`)`
|
||||||
|
|
||||||
|
Specifies the default behavior of a matching mock function call.
|
||||||
|
|
||||||
|
The parameter *`action`* represents the
|
||||||
|
[action](../gmock_for_dummies.md#actions-what-should-it-do) that the function
|
||||||
|
call will perform. See the [Actions Reference](actions.md) for a list of
|
||||||
|
built-in actions.
|
||||||
|
|
||||||
|
For example, the following code specifies that by default, a call to
|
||||||
|
`my_mock.Greet()` will return `"hello"`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Return;
|
||||||
|
...
|
||||||
|
ON_CALL(my_mock, Greet())
|
||||||
|
.WillByDefault(Return("hello"));
|
||||||
|
```
|
||||||
|
|
||||||
|
The action specified by `WillByDefault` is superseded by the actions specified
|
||||||
|
on a matching `EXPECT_CALL` statement, if any. See the
|
||||||
|
[`WillOnce`](#EXPECT_CALL.WillOnce) and
|
||||||
|
[`WillRepeatedly`](#EXPECT_CALL.WillRepeatedly) clauses of `EXPECT_CALL`.
|
||||||
|
|
||||||
|
The `WillByDefault` clause must be used exactly once with each `ON_CALL`
|
||||||
|
statement.
|
||||||
|
|
||||||
|
## Classes {#classes}
|
||||||
|
|
||||||
|
GoogleTest defines the following classes for working with mocks.
|
||||||
|
|
||||||
|
### DefaultValue {#DefaultValue}
|
||||||
|
|
||||||
|
`::testing::DefaultValue<T>`
|
||||||
|
|
||||||
|
Allows a user to specify the default value for a type `T` that is both copyable
|
||||||
|
and publicly destructible (i.e. anything that can be used as a function return
|
||||||
|
type). For mock functions with a return type of `T`, this default value is
|
||||||
|
returned from function calls that do not specify an action.
|
||||||
|
|
||||||
|
Provides the static methods `Set()`, `SetFactory()`, and `Clear()` to manage the
|
||||||
|
default value:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Sets the default value to be returned. T must be copy constructible.
|
||||||
|
DefaultValue<T>::Set(value);
|
||||||
|
|
||||||
|
// Sets a factory. Will be invoked on demand. T must be move constructible.
|
||||||
|
T MakeT();
|
||||||
|
DefaultValue<T>::SetFactory(&MakeT);
|
||||||
|
|
||||||
|
// Unsets the default value.
|
||||||
|
DefaultValue<T>::Clear();
|
||||||
|
```
|
||||||
|
|
||||||
|
### NiceMock {#NiceMock}
|
||||||
|
|
||||||
|
`::testing::NiceMock<T>`
|
||||||
|
|
||||||
|
Represents a mock object that suppresses warnings on
|
||||||
|
[uninteresting calls](../gmock_cook_book.md#uninteresting-vs-unexpected). The
|
||||||
|
template parameter `T` is any mock class, except for another `NiceMock`,
|
||||||
|
`NaggyMock`, or `StrictMock`.
|
||||||
|
|
||||||
|
Usage of `NiceMock<T>` is analogous to usage of `T`. `NiceMock<T>` is a subclass
|
||||||
|
of `T`, so it can be used wherever an object of type `T` is accepted. In
|
||||||
|
addition, `NiceMock<T>` can be constructed with any arguments that a constructor
|
||||||
|
of `T` accepts.
|
||||||
|
|
||||||
|
For example, the following code suppresses warnings on the mock `my_mock` of
|
||||||
|
type `MockClass` if a method other than `DoSomething()` is called:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::NiceMock;
|
||||||
|
...
|
||||||
|
NiceMock<MockClass> my_mock("some", "args");
|
||||||
|
EXPECT_CALL(my_mock, DoSomething());
|
||||||
|
... code that uses my_mock ...
|
||||||
|
```
|
||||||
|
|
||||||
|
`NiceMock<T>` only works for mock methods defined using the `MOCK_METHOD` macro
|
||||||
|
directly in the definition of class `T`. If a mock method is defined in a base
|
||||||
|
class of `T`, a warning might still be generated.
|
||||||
|
|
||||||
|
`NiceMock<T>` might not work correctly if the destructor of `T` is not virtual.
|
||||||
|
|
||||||
|
### NaggyMock {#NaggyMock}
|
||||||
|
|
||||||
|
`::testing::NaggyMock<T>`
|
||||||
|
|
||||||
|
Represents a mock object that generates warnings on
|
||||||
|
[uninteresting calls](../gmock_cook_book.md#uninteresting-vs-unexpected). The
|
||||||
|
template parameter `T` is any mock class, except for another `NiceMock`,
|
||||||
|
`NaggyMock`, or `StrictMock`.
|
||||||
|
|
||||||
|
Usage of `NaggyMock<T>` is analogous to usage of `T`. `NaggyMock<T>` is a
|
||||||
|
subclass of `T`, so it can be used wherever an object of type `T` is accepted.
|
||||||
|
In addition, `NaggyMock<T>` can be constructed with any arguments that a
|
||||||
|
constructor of `T` accepts.
|
||||||
|
|
||||||
|
For example, the following code generates warnings on the mock `my_mock` of type
|
||||||
|
`MockClass` if a method other than `DoSomething()` is called:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::NaggyMock;
|
||||||
|
...
|
||||||
|
NaggyMock<MockClass> my_mock("some", "args");
|
||||||
|
EXPECT_CALL(my_mock, DoSomething());
|
||||||
|
... code that uses my_mock ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Mock objects of type `T` by default behave the same way as `NaggyMock<T>`.
|
||||||
|
|
||||||
|
### StrictMock {#StrictMock}
|
||||||
|
|
||||||
|
`::testing::StrictMock<T>`
|
||||||
|
|
||||||
|
Represents a mock object that generates test failures on
|
||||||
|
[uninteresting calls](../gmock_cook_book.md#uninteresting-vs-unexpected). The
|
||||||
|
template parameter `T` is any mock class, except for another `NiceMock`,
|
||||||
|
`NaggyMock`, or `StrictMock`.
|
||||||
|
|
||||||
|
Usage of `StrictMock<T>` is analogous to usage of `T`. `StrictMock<T>` is a
|
||||||
|
subclass of `T`, so it can be used wherever an object of type `T` is accepted.
|
||||||
|
In addition, `StrictMock<T>` can be constructed with any arguments that a
|
||||||
|
constructor of `T` accepts.
|
||||||
|
|
||||||
|
For example, the following code generates a test failure on the mock `my_mock`
|
||||||
|
of type `MockClass` if a method other than `DoSomething()` is called:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::StrictMock;
|
||||||
|
...
|
||||||
|
StrictMock<MockClass> my_mock("some", "args");
|
||||||
|
EXPECT_CALL(my_mock, DoSomething());
|
||||||
|
... code that uses my_mock ...
|
||||||
|
```
|
||||||
|
|
||||||
|
`StrictMock<T>` only works for mock methods defined using the `MOCK_METHOD`
|
||||||
|
macro directly in the definition of class `T`. If a mock method is defined in a
|
||||||
|
base class of `T`, a failure might not be generated.
|
||||||
|
|
||||||
|
`StrictMock<T>` might not work correctly if the destructor of `T` is not
|
||||||
|
virtual.
|
||||||
|
|
||||||
|
### Sequence {#Sequence}
|
||||||
|
|
||||||
|
`::testing::Sequence`
|
||||||
|
|
||||||
|
Represents a chronological sequence of expectations. See the
|
||||||
|
[`InSequence`](#EXPECT_CALL.InSequence) clause of `EXPECT_CALL` for usage.
|
||||||
|
|
||||||
|
### InSequence {#InSequence}
|
||||||
|
|
||||||
|
`::testing::InSequence`
|
||||||
|
|
||||||
|
An object of this type causes all expectations encountered in its scope to be
|
||||||
|
put in an anonymous sequence.
|
||||||
|
|
||||||
|
This allows more convenient expression of multiple expectations in a single
|
||||||
|
sequence:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::InSequence;
|
||||||
|
{
|
||||||
|
InSequence seq;
|
||||||
|
|
||||||
|
// The following are expected to occur in the order declared.
|
||||||
|
EXPECT_CALL(...);
|
||||||
|
EXPECT_CALL(...);
|
||||||
|
...
|
||||||
|
EXPECT_CALL(...);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The name of the `InSequence` object does not matter.
|
||||||
|
|
||||||
|
### Expectation {#Expectation}
|
||||||
|
|
||||||
|
`::testing::Expectation`
|
||||||
|
|
||||||
|
Represents a mock function call expectation as created by
|
||||||
|
[`EXPECT_CALL`](#EXPECT_CALL):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::Expectation;
|
||||||
|
Expectation my_expectation = EXPECT_CALL(...);
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful for specifying sequences of expectations; see the
|
||||||
|
[`After`](#EXPECT_CALL.After) clause of `EXPECT_CALL`.
|
||||||
|
|
||||||
|
### ExpectationSet {#ExpectationSet}
|
||||||
|
|
||||||
|
`::testing::ExpectationSet`
|
||||||
|
|
||||||
|
Represents a set of mock function call expectations.
|
||||||
|
|
||||||
|
Use the `+=` operator to add [`Expectation`](#Expectation) objects to the set:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using ::testing::ExpectationSet;
|
||||||
|
ExpectationSet my_expectations;
|
||||||
|
my_expectations += EXPECT_CALL(...);
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful for specifying sequences of expectations; see the
|
||||||
|
[`After`](#EXPECT_CALL.After) clause of `EXPECT_CALL`.
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,22 @@
|
||||||
|
# Googletest Samples
|
||||||
|
|
||||||
|
If you're like us, you'd like to look at
|
||||||
|
[googletest samples.](https://github.com/google/googletest/blob/main/googletest/samples)
|
||||||
|
The sample directory has a number of well-commented samples showing how to use a
|
||||||
|
variety of googletest features.
|
||||||
|
|
||||||
|
* Sample #1 shows the basic steps of using googletest to test C++ functions.
|
||||||
|
* Sample #2 shows a more complex unit test for a class with multiple member
|
||||||
|
functions.
|
||||||
|
* Sample #3 uses a test fixture.
|
||||||
|
* Sample #4 teaches you how to use googletest and `googletest.h` together to
|
||||||
|
get the best of both libraries.
|
||||||
|
* Sample #5 puts shared testing logic in a base test fixture, and reuses it in
|
||||||
|
derived fixtures.
|
||||||
|
* Sample #6 demonstrates type-parameterized tests.
|
||||||
|
* Sample #7 teaches the basics of value-parameterized tests.
|
||||||
|
* Sample #8 shows using `Combine()` in value-parameterized tests.
|
||||||
|
* Sample #9 shows use of the listener API to modify Google Test's console
|
||||||
|
output and the use of its reflection API to inspect test results.
|
||||||
|
* Sample #10 shows use of the listener API to implement a primitive memory
|
||||||
|
leak checker.
|
|
@ -0,0 +1,209 @@
|
||||||
|
########################################################################
|
||||||
|
# Note: CMake support is community-based. The maintainers do not use CMake
|
||||||
|
# internally.
|
||||||
|
#
|
||||||
|
# CMake build script for Google Mock.
|
||||||
|
#
|
||||||
|
# To run the tests for Google Mock itself on Linux, use 'make test' or
|
||||||
|
# ctest. You can select which tests to run using 'ctest -R regex'.
|
||||||
|
# For more options, run 'ctest --help'.
|
||||||
|
|
||||||
|
option(gmock_build_tests "Build all of Google Mock's own tests." OFF)
|
||||||
|
|
||||||
|
# A directory to find Google Test sources.
|
||||||
|
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/gtest/CMakeLists.txt")
|
||||||
|
set(gtest_dir gtest)
|
||||||
|
else()
|
||||||
|
set(gtest_dir ../googletest)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Defines pre_project_set_up_hermetic_build() and set_up_hermetic_build().
|
||||||
|
include("${gtest_dir}/cmake/hermetic_build.cmake" OPTIONAL)
|
||||||
|
|
||||||
|
if (COMMAND pre_project_set_up_hermetic_build)
|
||||||
|
# Google Test also calls hermetic setup functions from add_subdirectory,
|
||||||
|
# although its changes will not affect things at the current scope.
|
||||||
|
pre_project_set_up_hermetic_build()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
#
|
||||||
|
# Project-wide settings
|
||||||
|
|
||||||
|
# Name of the project.
|
||||||
|
#
|
||||||
|
# CMake files in this project can refer to the root source directory
|
||||||
|
# as ${gmock_SOURCE_DIR} and to the root binary directory as
|
||||||
|
# ${gmock_BINARY_DIR}.
|
||||||
|
# Language "C" is required for find_package(Threads).
|
||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
project(gmock VERSION ${GOOGLETEST_VERSION} LANGUAGES CXX C)
|
||||||
|
|
||||||
|
if (COMMAND set_up_hermetic_build)
|
||||||
|
set_up_hermetic_build()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Instructs CMake to process Google Test's CMakeLists.txt and add its
|
||||||
|
# targets to the current scope. We are placing Google Test's binary
|
||||||
|
# directory in a subdirectory of our own as VC compilation may break
|
||||||
|
# if they are the same (the default).
|
||||||
|
add_subdirectory("${gtest_dir}" "${gmock_BINARY_DIR}/${gtest_dir}")
|
||||||
|
|
||||||
|
|
||||||
|
# These commands only run if this is the main project
|
||||||
|
if(CMAKE_PROJECT_NAME STREQUAL "gmock" OR CMAKE_PROJECT_NAME STREQUAL "googletest-distribution")
|
||||||
|
# BUILD_SHARED_LIBS is a standard CMake variable, but we declare it here to
|
||||||
|
# make it prominent in the GUI.
|
||||||
|
option(BUILD_SHARED_LIBS "Build shared libraries (DLLs)." OFF)
|
||||||
|
else()
|
||||||
|
mark_as_advanced(gmock_build_tests)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Although Google Test's CMakeLists.txt calls this function, the
|
||||||
|
# changes there don't affect the current scope. Therefore we have to
|
||||||
|
# call it again here.
|
||||||
|
config_compiler_and_linker() # from ${gtest_dir}/cmake/internal_utils.cmake
|
||||||
|
|
||||||
|
# Adds Google Mock's and Google Test's header directories to the search path.
|
||||||
|
set(gmock_build_include_dirs
|
||||||
|
"${gmock_SOURCE_DIR}/include"
|
||||||
|
"${gmock_SOURCE_DIR}"
|
||||||
|
"${gtest_SOURCE_DIR}/include"
|
||||||
|
# This directory is needed to build directly from Google Test sources.
|
||||||
|
"${gtest_SOURCE_DIR}")
|
||||||
|
include_directories(${gmock_build_include_dirs})
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
#
|
||||||
|
# Defines the gmock & gmock_main libraries. User tests should link
|
||||||
|
# with one of them.
|
||||||
|
|
||||||
|
# Google Mock libraries. We build them using more strict warnings than what
|
||||||
|
# are used for other targets, to ensure that Google Mock can be compiled by
|
||||||
|
# a user aggressive about warnings.
|
||||||
|
if (MSVC)
|
||||||
|
cxx_library(gmock
|
||||||
|
"${cxx_strict}"
|
||||||
|
"${gtest_dir}/src/gtest-all.cc"
|
||||||
|
src/gmock-all.cc)
|
||||||
|
|
||||||
|
cxx_library(gmock_main
|
||||||
|
"${cxx_strict}"
|
||||||
|
"${gtest_dir}/src/gtest-all.cc"
|
||||||
|
src/gmock-all.cc
|
||||||
|
src/gmock_main.cc)
|
||||||
|
else()
|
||||||
|
cxx_library(gmock "${cxx_strict}" src/gmock-all.cc)
|
||||||
|
target_link_libraries(gmock PUBLIC gtest)
|
||||||
|
set_target_properties(gmock PROPERTIES VERSION ${GOOGLETEST_VERSION})
|
||||||
|
cxx_library(gmock_main "${cxx_strict}" src/gmock_main.cc)
|
||||||
|
target_link_libraries(gmock_main PUBLIC gmock)
|
||||||
|
set_target_properties(gmock_main PROPERTIES VERSION ${GOOGLETEST_VERSION})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
string(REPLACE ";" "$<SEMICOLON>" dirs "${gmock_build_include_dirs}")
|
||||||
|
target_include_directories(gmock SYSTEM INTERFACE
|
||||||
|
"$<BUILD_INTERFACE:${dirs}>"
|
||||||
|
"$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/${CMAKE_INSTALL_INCLUDEDIR}>")
|
||||||
|
target_include_directories(gmock_main SYSTEM INTERFACE
|
||||||
|
"$<BUILD_INTERFACE:${dirs}>"
|
||||||
|
"$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/${CMAKE_INSTALL_INCLUDEDIR}>")
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
#
|
||||||
|
# Install rules
|
||||||
|
install_project(gmock gmock_main)
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
#
|
||||||
|
# Google Mock's own tests.
|
||||||
|
#
|
||||||
|
# You can skip this section if you aren't interested in testing
|
||||||
|
# Google Mock itself.
|
||||||
|
#
|
||||||
|
# The tests are not built by default. To build them, set the
|
||||||
|
# gmock_build_tests option to ON. You can do it by running ccmake
|
||||||
|
# or specifying the -Dgmock_build_tests=ON flag when running cmake.
|
||||||
|
|
||||||
|
if (gmock_build_tests)
|
||||||
|
# This must be set in the root directory for the tests to be run by
|
||||||
|
# 'make test' or ctest.
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
if (MINGW OR CYGWIN)
|
||||||
|
add_compile_options("-Wa,-mbig-obj")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
# C++ tests built with standard compiler flags.
|
||||||
|
|
||||||
|
cxx_test(gmock-actions_test gmock_main)
|
||||||
|
cxx_test(gmock-cardinalities_test gmock_main)
|
||||||
|
cxx_test(gmock_ex_test gmock_main)
|
||||||
|
cxx_test(gmock-function-mocker_test gmock_main)
|
||||||
|
cxx_test(gmock-internal-utils_test gmock_main)
|
||||||
|
cxx_test(gmock-matchers-arithmetic_test gmock_main)
|
||||||
|
cxx_test(gmock-matchers-comparisons_test gmock_main)
|
||||||
|
cxx_test(gmock-matchers-containers_test gmock_main)
|
||||||
|
cxx_test(gmock-matchers-misc_test gmock_main)
|
||||||
|
cxx_test(gmock-more-actions_test gmock_main)
|
||||||
|
cxx_test(gmock-nice-strict_test gmock_main)
|
||||||
|
cxx_test(gmock-port_test gmock_main)
|
||||||
|
cxx_test(gmock-spec-builders_test gmock_main)
|
||||||
|
cxx_test(gmock_link_test gmock_main test/gmock_link2_test.cc)
|
||||||
|
cxx_test(gmock_test gmock_main)
|
||||||
|
|
||||||
|
if (DEFINED GTEST_HAS_PTHREAD)
|
||||||
|
cxx_test(gmock_stress_test gmock)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# gmock_all_test is commented to save time building and running tests.
|
||||||
|
# Uncomment if necessary.
|
||||||
|
# cxx_test(gmock_all_test gmock_main)
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
# C++ tests built with non-standard compiler flags.
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
cxx_library(gmock_main_no_exception "${cxx_no_exception}"
|
||||||
|
"${gtest_dir}/src/gtest-all.cc" src/gmock-all.cc src/gmock_main.cc)
|
||||||
|
|
||||||
|
cxx_library(gmock_main_no_rtti "${cxx_no_rtti}"
|
||||||
|
"${gtest_dir}/src/gtest-all.cc" src/gmock-all.cc src/gmock_main.cc)
|
||||||
|
|
||||||
|
else()
|
||||||
|
cxx_library(gmock_main_no_exception "${cxx_no_exception}" src/gmock_main.cc)
|
||||||
|
target_link_libraries(gmock_main_no_exception PUBLIC gmock)
|
||||||
|
|
||||||
|
cxx_library(gmock_main_no_rtti "${cxx_no_rtti}" src/gmock_main.cc)
|
||||||
|
target_link_libraries(gmock_main_no_rtti PUBLIC gmock)
|
||||||
|
endif()
|
||||||
|
cxx_test_with_flags(gmock-more-actions_no_exception_test "${cxx_no_exception}"
|
||||||
|
gmock_main_no_exception test/gmock-more-actions_test.cc)
|
||||||
|
|
||||||
|
cxx_test_with_flags(gmock_no_rtti_test "${cxx_no_rtti}"
|
||||||
|
gmock_main_no_rtti test/gmock-spec-builders_test.cc)
|
||||||
|
|
||||||
|
cxx_shared_library(shared_gmock_main "${cxx_default}"
|
||||||
|
"${gtest_dir}/src/gtest-all.cc" src/gmock-all.cc src/gmock_main.cc)
|
||||||
|
|
||||||
|
# Tests that a binary can be built with Google Mock as a shared library. On
|
||||||
|
# some system configurations, it may not possible to run the binary without
|
||||||
|
# knowing more details about the system configurations. We do not try to run
|
||||||
|
# this binary. To get a more robust shared library coverage, configure with
|
||||||
|
# -DBUILD_SHARED_LIBS=ON.
|
||||||
|
cxx_executable_with_flags(shared_gmock_test_ "${cxx_default}"
|
||||||
|
shared_gmock_main test/gmock-spec-builders_test.cc)
|
||||||
|
set_target_properties(shared_gmock_test_
|
||||||
|
PROPERTIES
|
||||||
|
COMPILE_DEFINITIONS "GTEST_LINKED_AS_SHARED_LIBRARY=1")
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
# Python tests.
|
||||||
|
|
||||||
|
cxx_executable(gmock_leak_test_ test gmock_main)
|
||||||
|
py_test(gmock_leak_test)
|
||||||
|
|
||||||
|
cxx_executable(gmock_output_test_ test gmock)
|
||||||
|
py_test(gmock_output_test)
|
||||||
|
endif()
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Googletest Mocking (gMock) Framework
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
Google's framework for writing and using C++ mock classes. It can help you
|
||||||
|
derive better designs of your system and write better tests.
|
||||||
|
|
||||||
|
It is inspired by:
|
||||||
|
|
||||||
|
* [jMock](http://www.jmock.org/)
|
||||||
|
* [EasyMock](http://www.easymock.org/)
|
||||||
|
* [Hamcrest](http://code.google.com/p/hamcrest/)
|
||||||
|
|
||||||
|
It is designed with C++'s specifics in mind.
|
||||||
|
|
||||||
|
gMock:
|
||||||
|
|
||||||
|
- Provides a declarative syntax for defining mocks.
|
||||||
|
- Can define partial (hybrid) mocks, which are a cross of real and mock
|
||||||
|
objects.
|
||||||
|
- Handles functions of arbitrary types and overloaded functions.
|
||||||
|
- Comes with a rich set of matchers for validating function arguments.
|
||||||
|
- Uses an intuitive syntax for controlling the behavior of a mock.
|
||||||
|
- Does automatic verification of expectations (no record-and-replay needed).
|
||||||
|
- Allows arbitrary (partial) ordering constraints on function calls to be
|
||||||
|
expressed.
|
||||||
|
- Lets a user extend it by defining new matchers and actions.
|
||||||
|
- Does not use exceptions.
|
||||||
|
- Is easy to learn and use.
|
||||||
|
|
||||||
|
Details and examples can be found here:
|
||||||
|
|
||||||
|
* [gMock for Dummies](https://google.github.io/googletest/gmock_for_dummies.html)
|
||||||
|
* [Legacy gMock FAQ](https://google.github.io/googletest/gmock_faq.html)
|
||||||
|
* [gMock Cookbook](https://google.github.io/googletest/gmock_cook_book.html)
|
||||||
|
* [gMock Cheat Sheet](https://google.github.io/googletest/gmock_cheat_sheet.html)
|
||||||
|
|
||||||
|
GoogleMock is a part of
|
||||||
|
[GoogleTest C++ testing framework](http://github.com/google/googletest/) and a
|
||||||
|
subject to the same requirements.
|
|
@ -0,0 +1,10 @@
|
||||||
|
libdir=@CMAKE_INSTALL_FULL_LIBDIR@
|
||||||
|
includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
|
||||||
|
|
||||||
|
Name: gmock
|
||||||
|
Description: GoogleMock (without main() function)
|
||||||
|
Version: @PROJECT_VERSION@
|
||||||
|
URL: https://github.com/google/googletest
|
||||||
|
Requires: gtest = @PROJECT_VERSION@
|
||||||
|
Libs: -L${libdir} -lgmock @CMAKE_THREAD_LIBS_INIT@
|
||||||
|
Cflags: -I${includedir} @GTEST_HAS_PTHREAD_MACRO@
|
|
@ -0,0 +1,10 @@
|
||||||
|
libdir=@CMAKE_INSTALL_FULL_LIBDIR@
|
||||||
|
includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
|
||||||
|
|
||||||
|
Name: gmock_main
|
||||||
|
Description: GoogleMock (with main() function)
|
||||||
|
Version: @PROJECT_VERSION@
|
||||||
|
URL: https://github.com/google/googletest
|
||||||
|
Requires: gmock = @PROJECT_VERSION@
|
||||||
|
Libs: -L${libdir} -lgmock_main @CMAKE_THREAD_LIBS_INIT@
|
||||||
|
Cflags: -I${includedir} @GTEST_HAS_PTHREAD_MACRO@
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,159 @@
|
||||||
|
// Copyright 2007, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Google Mock - a framework for writing C++ mock classes.
|
||||||
|
//
|
||||||
|
// This file implements some commonly used cardinalities. More
|
||||||
|
// cardinalities can be defined by the user implementing the
|
||||||
|
// CardinalityInterface interface if necessary.
|
||||||
|
|
||||||
|
// IWYU pragma: private, include "gmock/gmock.h"
|
||||||
|
// IWYU pragma: friend gmock/.*
|
||||||
|
|
||||||
|
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_
|
||||||
|
#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <ostream> // NOLINT
|
||||||
|
|
||||||
|
#include "gmock/internal/gmock-port.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
|
||||||
|
/* class A needs to have dll-interface to be used by clients of class B */)
|
||||||
|
|
||||||
|
namespace testing {
|
||||||
|
|
||||||
|
// To implement a cardinality Foo, define:
|
||||||
|
// 1. a class FooCardinality that implements the
|
||||||
|
// CardinalityInterface interface, and
|
||||||
|
// 2. a factory function that creates a Cardinality object from a
|
||||||
|
// const FooCardinality*.
|
||||||
|
//
|
||||||
|
// The two-level delegation design follows that of Matcher, providing
|
||||||
|
// consistency for extension developers. It also eases ownership
|
||||||
|
// management as Cardinality objects can now be copied like plain values.
|
||||||
|
|
||||||
|
// The implementation of a cardinality.
|
||||||
|
class CardinalityInterface {
|
||||||
|
public:
|
||||||
|
virtual ~CardinalityInterface() = default;
|
||||||
|
|
||||||
|
// Conservative estimate on the lower/upper bound of the number of
|
||||||
|
// calls allowed.
|
||||||
|
virtual int ConservativeLowerBound() const { return 0; }
|
||||||
|
virtual int ConservativeUpperBound() const { return INT_MAX; }
|
||||||
|
|
||||||
|
// Returns true if and only if call_count calls will satisfy this
|
||||||
|
// cardinality.
|
||||||
|
virtual bool IsSatisfiedByCallCount(int call_count) const = 0;
|
||||||
|
|
||||||
|
// Returns true if and only if call_count calls will saturate this
|
||||||
|
// cardinality.
|
||||||
|
virtual bool IsSaturatedByCallCount(int call_count) const = 0;
|
||||||
|
|
||||||
|
// Describes self to an ostream.
|
||||||
|
virtual void DescribeTo(::std::ostream* os) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A Cardinality is a copyable and IMMUTABLE (except by assignment)
|
||||||
|
// object that specifies how many times a mock function is expected to
|
||||||
|
// be called. The implementation of Cardinality is just a std::shared_ptr
|
||||||
|
// to const CardinalityInterface. Don't inherit from Cardinality!
|
||||||
|
class GTEST_API_ Cardinality {
|
||||||
|
public:
|
||||||
|
// Constructs a null cardinality. Needed for storing Cardinality
|
||||||
|
// objects in STL containers.
|
||||||
|
Cardinality() = default;
|
||||||
|
|
||||||
|
// Constructs a Cardinality from its implementation.
|
||||||
|
explicit Cardinality(const CardinalityInterface* impl) : impl_(impl) {}
|
||||||
|
|
||||||
|
// Conservative estimate on the lower/upper bound of the number of
|
||||||
|
// calls allowed.
|
||||||
|
int ConservativeLowerBound() const { return impl_->ConservativeLowerBound(); }
|
||||||
|
int ConservativeUpperBound() const { return impl_->ConservativeUpperBound(); }
|
||||||
|
|
||||||
|
// Returns true if and only if call_count calls will satisfy this
|
||||||
|
// cardinality.
|
||||||
|
bool IsSatisfiedByCallCount(int call_count) const {
|
||||||
|
return impl_->IsSatisfiedByCallCount(call_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if and only if call_count calls will saturate this
|
||||||
|
// cardinality.
|
||||||
|
bool IsSaturatedByCallCount(int call_count) const {
|
||||||
|
return impl_->IsSaturatedByCallCount(call_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if and only if call_count calls will over-saturate this
|
||||||
|
// cardinality, i.e. exceed the maximum number of allowed calls.
|
||||||
|
bool IsOverSaturatedByCallCount(int call_count) const {
|
||||||
|
return impl_->IsSaturatedByCallCount(call_count) &&
|
||||||
|
!impl_->IsSatisfiedByCallCount(call_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Describes self to an ostream
|
||||||
|
void DescribeTo(::std::ostream* os) const { impl_->DescribeTo(os); }
|
||||||
|
|
||||||
|
// Describes the given actual call count to an ostream.
|
||||||
|
static void DescribeActualCallCountTo(int actual_call_count,
|
||||||
|
::std::ostream* os);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<const CardinalityInterface> impl_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Creates a cardinality that allows at least n calls.
|
||||||
|
GTEST_API_ Cardinality AtLeast(int n);
|
||||||
|
|
||||||
|
// Creates a cardinality that allows at most n calls.
|
||||||
|
GTEST_API_ Cardinality AtMost(int n);
|
||||||
|
|
||||||
|
// Creates a cardinality that allows any number of calls.
|
||||||
|
GTEST_API_ Cardinality AnyNumber();
|
||||||
|
|
||||||
|
// Creates a cardinality that allows between min and max calls.
|
||||||
|
GTEST_API_ Cardinality Between(int min, int max);
|
||||||
|
|
||||||
|
// Creates a cardinality that allows exactly n calls.
|
||||||
|
GTEST_API_ Cardinality Exactly(int n);
|
||||||
|
|
||||||
|
// Creates a cardinality from its implementation.
|
||||||
|
inline Cardinality MakeCardinality(const CardinalityInterface* c) {
|
||||||
|
return Cardinality(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace testing
|
||||||
|
|
||||||
|
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
|
||||||
|
|
||||||
|
#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_CARDINALITIES_H_
|
|
@ -0,0 +1,518 @@
|
||||||
|
// Copyright 2007, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Google Mock - a framework for writing C++ mock classes.
|
||||||
|
//
|
||||||
|
// This file implements MOCK_METHOD.
|
||||||
|
|
||||||
|
// IWYU pragma: private, include "gmock/gmock.h"
|
||||||
|
// IWYU pragma: friend gmock/.*
|
||||||
|
|
||||||
|
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_FUNCTION_MOCKER_H_
|
||||||
|
#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_FUNCTION_MOCKER_H_
|
||||||
|
|
||||||
|
#include <type_traits> // IWYU pragma: keep
|
||||||
|
#include <utility> // IWYU pragma: keep
|
||||||
|
|
||||||
|
#include "gmock/gmock-spec-builders.h"
|
||||||
|
#include "gmock/internal/gmock-internal-utils.h"
|
||||||
|
#include "gmock/internal/gmock-pp.h"
|
||||||
|
|
||||||
|
namespace testing {
|
||||||
|
namespace internal {
|
||||||
|
template <typename T>
|
||||||
|
using identity_t = T;
|
||||||
|
|
||||||
|
template <typename Pattern>
|
||||||
|
struct ThisRefAdjuster {
|
||||||
|
template <typename T>
|
||||||
|
using AdjustT = typename std::conditional<
|
||||||
|
std::is_const<typename std::remove_reference<Pattern>::type>::value,
|
||||||
|
typename std::conditional<std::is_lvalue_reference<Pattern>::value,
|
||||||
|
const T&, const T&&>::type,
|
||||||
|
typename std::conditional<std::is_lvalue_reference<Pattern>::value, T&,
|
||||||
|
T&&>::type>::type;
|
||||||
|
|
||||||
|
template <typename MockType>
|
||||||
|
static AdjustT<MockType> Adjust(const MockType& mock) {
|
||||||
|
return static_cast<AdjustT<MockType>>(const_cast<MockType&>(mock));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr bool PrefixOf(const char* a, const char* b) {
|
||||||
|
return *a == 0 || (*a == *b && internal::PrefixOf(a + 1, b + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int N, int M>
|
||||||
|
constexpr bool StartsWith(const char (&prefix)[N], const char (&str)[M]) {
|
||||||
|
return N <= M && internal::PrefixOf(prefix, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int N, int M>
|
||||||
|
constexpr bool EndsWith(const char (&suffix)[N], const char (&str)[M]) {
|
||||||
|
return N <= M && internal::PrefixOf(suffix, str + M - N);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int N, int M>
|
||||||
|
constexpr bool Equals(const char (&a)[N], const char (&b)[M]) {
|
||||||
|
return N == M && internal::PrefixOf(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int N>
|
||||||
|
constexpr bool ValidateSpec(const char (&spec)[N]) {
|
||||||
|
return internal::Equals("const", spec) ||
|
||||||
|
internal::Equals("override", spec) ||
|
||||||
|
internal::Equals("final", spec) ||
|
||||||
|
internal::Equals("noexcept", spec) ||
|
||||||
|
(internal::StartsWith("noexcept(", spec) &&
|
||||||
|
internal::EndsWith(")", spec)) ||
|
||||||
|
internal::Equals("ref(&)", spec) ||
|
||||||
|
internal::Equals("ref(&&)", spec) ||
|
||||||
|
(internal::StartsWith("Calltype(", spec) &&
|
||||||
|
internal::EndsWith(")", spec));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
// The style guide prohibits "using" statements in a namespace scope
|
||||||
|
// inside a header file. However, the FunctionMocker class template
|
||||||
|
// is meant to be defined in the ::testing namespace. The following
|
||||||
|
// line is just a trick for working around a bug in MSVC 8.0, which
|
||||||
|
// cannot handle it if we define FunctionMocker in ::testing.
|
||||||
|
using internal::FunctionMocker;
|
||||||
|
} // namespace testing
|
||||||
|
|
||||||
|
#define MOCK_METHOD(...) \
|
||||||
|
GMOCK_INTERNAL_WARNING_PUSH() \
|
||||||
|
GMOCK_INTERNAL_WARNING_CLANG(ignored, "-Wunused-member-function") \
|
||||||
|
GMOCK_PP_VARIADIC_CALL(GMOCK_INTERNAL_MOCK_METHOD_ARG_, __VA_ARGS__) \
|
||||||
|
GMOCK_INTERNAL_WARNING_POP()
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_MOCK_METHOD_ARG_1(...) \
|
||||||
|
GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__)
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_MOCK_METHOD_ARG_2(...) \
|
||||||
|
GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__)
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_MOCK_METHOD_ARG_3(_Ret, _MethodName, _Args) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHOD_ARG_4(_Ret, _MethodName, _Args, ())
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_MOCK_METHOD_ARG_4(_Ret, _MethodName, _Args, _Spec) \
|
||||||
|
GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Args); \
|
||||||
|
GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Spec); \
|
||||||
|
GMOCK_INTERNAL_ASSERT_VALID_SIGNATURE( \
|
||||||
|
GMOCK_PP_NARG0 _Args, GMOCK_INTERNAL_SIGNATURE(_Ret, _Args)); \
|
||||||
|
GMOCK_INTERNAL_ASSERT_VALID_SPEC(_Spec) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHOD_IMPL( \
|
||||||
|
GMOCK_PP_NARG0 _Args, _MethodName, GMOCK_INTERNAL_HAS_CONST(_Spec), \
|
||||||
|
GMOCK_INTERNAL_HAS_OVERRIDE(_Spec), GMOCK_INTERNAL_HAS_FINAL(_Spec), \
|
||||||
|
GMOCK_INTERNAL_GET_NOEXCEPT_SPEC(_Spec), \
|
||||||
|
GMOCK_INTERNAL_GET_CALLTYPE_SPEC(_Spec), \
|
||||||
|
GMOCK_INTERNAL_GET_REF_SPEC(_Spec), \
|
||||||
|
(GMOCK_INTERNAL_SIGNATURE(_Ret, _Args)))
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_MOCK_METHOD_ARG_5(...) \
|
||||||
|
GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__)
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_MOCK_METHOD_ARG_6(...) \
|
||||||
|
GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__)
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_MOCK_METHOD_ARG_7(...) \
|
||||||
|
GMOCK_INTERNAL_WRONG_ARITY(__VA_ARGS__)
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_WRONG_ARITY(...) \
|
||||||
|
static_assert( \
|
||||||
|
false, \
|
||||||
|
"MOCK_METHOD must be called with 3 or 4 arguments. _Ret, " \
|
||||||
|
"_MethodName, _Args and optionally _Spec. _Args and _Spec must be " \
|
||||||
|
"enclosed in parentheses. If _Ret is a type with unprotected commas, " \
|
||||||
|
"it must also be enclosed in parentheses.")
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_ASSERT_PARENTHESIS(_Tuple) \
|
||||||
|
static_assert( \
|
||||||
|
GMOCK_PP_IS_ENCLOSED_PARENS(_Tuple), \
|
||||||
|
GMOCK_PP_STRINGIZE(_Tuple) " should be enclosed in parentheses.")
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_ASSERT_VALID_SIGNATURE(_N, ...) \
|
||||||
|
static_assert( \
|
||||||
|
std::is_function<__VA_ARGS__>::value, \
|
||||||
|
"Signature must be a function type, maybe return type contains " \
|
||||||
|
"unprotected comma."); \
|
||||||
|
static_assert( \
|
||||||
|
::testing::tuple_size<typename ::testing::internal::Function< \
|
||||||
|
__VA_ARGS__>::ArgumentTuple>::value == _N, \
|
||||||
|
"This method does not take " GMOCK_PP_STRINGIZE( \
|
||||||
|
_N) " arguments. Parenthesize all types with unprotected commas.")
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_ASSERT_VALID_SPEC(_Spec) \
|
||||||
|
GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_ASSERT_VALID_SPEC_ELEMENT, ~, _Spec)
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_MOCK_METHOD_IMPL(_N, _MethodName, _Constness, \
|
||||||
|
_Override, _Final, _NoexceptSpec, \
|
||||||
|
_CallType, _RefSpec, _Signature) \
|
||||||
|
typename ::testing::internal::Function<GMOCK_PP_REMOVE_PARENS( \
|
||||||
|
_Signature)>::Result \
|
||||||
|
GMOCK_INTERNAL_EXPAND(_CallType) \
|
||||||
|
_MethodName(GMOCK_PP_REPEAT(GMOCK_INTERNAL_PARAMETER, _Signature, _N)) \
|
||||||
|
GMOCK_PP_IF(_Constness, const, ) \
|
||||||
|
_RefSpec _NoexceptSpec GMOCK_PP_IF(_Override, override, ) \
|
||||||
|
GMOCK_PP_IF(_Final, final, ) { \
|
||||||
|
GMOCK_MOCKER_(_N, _Constness, _MethodName) \
|
||||||
|
.SetOwnerAndName(this, #_MethodName); \
|
||||||
|
return GMOCK_MOCKER_(_N, _Constness, _MethodName) \
|
||||||
|
.Invoke(GMOCK_PP_REPEAT(GMOCK_INTERNAL_FORWARD_ARG, _Signature, _N)); \
|
||||||
|
} \
|
||||||
|
::testing::MockSpec<GMOCK_PP_REMOVE_PARENS(_Signature)> gmock_##_MethodName( \
|
||||||
|
GMOCK_PP_REPEAT(GMOCK_INTERNAL_MATCHER_PARAMETER, _Signature, _N)) \
|
||||||
|
GMOCK_PP_IF(_Constness, const, ) _RefSpec { \
|
||||||
|
GMOCK_MOCKER_(_N, _Constness, _MethodName).RegisterOwner(this); \
|
||||||
|
return GMOCK_MOCKER_(_N, _Constness, _MethodName) \
|
||||||
|
.With(GMOCK_PP_REPEAT(GMOCK_INTERNAL_MATCHER_ARGUMENT, , _N)); \
|
||||||
|
} \
|
||||||
|
::testing::MockSpec<GMOCK_PP_REMOVE_PARENS(_Signature)> gmock_##_MethodName( \
|
||||||
|
const ::testing::internal::WithoutMatchers&, \
|
||||||
|
GMOCK_PP_IF(_Constness, const, )::testing::internal::Function< \
|
||||||
|
GMOCK_PP_REMOVE_PARENS(_Signature)>*) const _RefSpec _NoexceptSpec { \
|
||||||
|
return ::testing::internal::ThisRefAdjuster<GMOCK_PP_IF( \
|
||||||
|
_Constness, const, ) int _RefSpec>::Adjust(*this) \
|
||||||
|
.gmock_##_MethodName(GMOCK_PP_REPEAT( \
|
||||||
|
GMOCK_INTERNAL_A_MATCHER_ARGUMENT, _Signature, _N)); \
|
||||||
|
} \
|
||||||
|
mutable ::testing::FunctionMocker<GMOCK_PP_REMOVE_PARENS(_Signature)> \
|
||||||
|
GMOCK_MOCKER_(_N, _Constness, _MethodName)
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_EXPAND(...) __VA_ARGS__
|
||||||
|
|
||||||
|
// Valid modifiers.
|
||||||
|
#define GMOCK_INTERNAL_HAS_CONST(_Tuple) \
|
||||||
|
GMOCK_PP_HAS_COMMA(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_DETECT_CONST, ~, _Tuple))
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_HAS_OVERRIDE(_Tuple) \
|
||||||
|
GMOCK_PP_HAS_COMMA( \
|
||||||
|
GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_DETECT_OVERRIDE, ~, _Tuple))
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_HAS_FINAL(_Tuple) \
|
||||||
|
GMOCK_PP_HAS_COMMA(GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_DETECT_FINAL, ~, _Tuple))
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_GET_NOEXCEPT_SPEC(_Tuple) \
|
||||||
|
GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_NOEXCEPT_SPEC_IF_NOEXCEPT, ~, _Tuple)
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_NOEXCEPT_SPEC_IF_NOEXCEPT(_i, _, _elem) \
|
||||||
|
GMOCK_PP_IF( \
|
||||||
|
GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_NOEXCEPT(_i, _, _elem)), \
|
||||||
|
_elem, )
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_GET_CALLTYPE_SPEC(_Tuple) \
|
||||||
|
GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_CALLTYPE_SPEC_IF_CALLTYPE, ~, _Tuple)
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_CALLTYPE_SPEC_IF_CALLTYPE(_i, _, _elem) \
|
||||||
|
GMOCK_PP_IF( \
|
||||||
|
GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_CALLTYPE(_i, _, _elem)), \
|
||||||
|
GMOCK_PP_CAT(GMOCK_INTERNAL_UNPACK_, _elem), )
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_GET_REF_SPEC(_Tuple) \
|
||||||
|
GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_REF_SPEC_IF_REF, ~, _Tuple)
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_REF_SPEC_IF_REF(_i, _, _elem) \
|
||||||
|
GMOCK_PP_IF(GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_REF(_i, _, _elem)), \
|
||||||
|
GMOCK_PP_CAT(GMOCK_INTERNAL_UNPACK_, _elem), )
|
||||||
|
|
||||||
|
#ifdef GMOCK_INTERNAL_STRICT_SPEC_ASSERT
|
||||||
|
#define GMOCK_INTERNAL_ASSERT_VALID_SPEC_ELEMENT(_i, _, _elem) \
|
||||||
|
static_assert( \
|
||||||
|
::testing::internal::ValidateSpec(GMOCK_PP_STRINGIZE(_elem)), \
|
||||||
|
"Token \'" GMOCK_PP_STRINGIZE( \
|
||||||
|
_elem) "\' cannot be recognized as a valid specification " \
|
||||||
|
"modifier. Is a ',' missing?");
|
||||||
|
#else
|
||||||
|
#define GMOCK_INTERNAL_ASSERT_VALID_SPEC_ELEMENT(_i, _, _elem) \
|
||||||
|
static_assert( \
|
||||||
|
(GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_CONST(_i, _, _elem)) + \
|
||||||
|
GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_OVERRIDE(_i, _, _elem)) + \
|
||||||
|
GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_FINAL(_i, _, _elem)) + \
|
||||||
|
GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_NOEXCEPT(_i, _, _elem)) + \
|
||||||
|
GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_REF(_i, _, _elem)) + \
|
||||||
|
GMOCK_PP_HAS_COMMA(GMOCK_INTERNAL_DETECT_CALLTYPE(_i, _, _elem))) == 1, \
|
||||||
|
GMOCK_PP_STRINGIZE( \
|
||||||
|
_elem) " cannot be recognized as a valid specification modifier.");
|
||||||
|
#endif // GMOCK_INTERNAL_STRICT_SPEC_ASSERT
|
||||||
|
|
||||||
|
// Modifiers implementation.
|
||||||
|
#define GMOCK_INTERNAL_DETECT_CONST(_i, _, _elem) \
|
||||||
|
GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_CONST_I_, _elem)
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_DETECT_CONST_I_const ,
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_DETECT_OVERRIDE(_i, _, _elem) \
|
||||||
|
GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_OVERRIDE_I_, _elem)
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_DETECT_OVERRIDE_I_override ,
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_DETECT_FINAL(_i, _, _elem) \
|
||||||
|
GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_FINAL_I_, _elem)
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_DETECT_FINAL_I_final ,
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_DETECT_NOEXCEPT(_i, _, _elem) \
|
||||||
|
GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_NOEXCEPT_I_, _elem)
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_DETECT_NOEXCEPT_I_noexcept ,
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_DETECT_REF(_i, _, _elem) \
|
||||||
|
GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_REF_I_, _elem)
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_DETECT_REF_I_ref ,
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_UNPACK_ref(x) x
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_DETECT_CALLTYPE(_i, _, _elem) \
|
||||||
|
GMOCK_PP_CAT(GMOCK_INTERNAL_DETECT_CALLTYPE_I_, _elem)
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_DETECT_CALLTYPE_I_Calltype ,
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_UNPACK_Calltype(...) __VA_ARGS__
|
||||||
|
|
||||||
|
// Note: The use of `identity_t` here allows _Ret to represent return types that
|
||||||
|
// would normally need to be specified in a different way. For example, a method
|
||||||
|
// returning a function pointer must be written as
|
||||||
|
//
|
||||||
|
// fn_ptr_return_t (*method(method_args_t...))(fn_ptr_args_t...)
|
||||||
|
//
|
||||||
|
// But we only support placing the return type at the beginning. To handle this,
|
||||||
|
// we wrap all calls in identity_t, so that a declaration will be expanded to
|
||||||
|
//
|
||||||
|
// identity_t<fn_ptr_return_t (*)(fn_ptr_args_t...)> method(method_args_t...)
|
||||||
|
//
|
||||||
|
// This allows us to work around the syntactic oddities of function/method
|
||||||
|
// types.
|
||||||
|
#define GMOCK_INTERNAL_SIGNATURE(_Ret, _Args) \
|
||||||
|
::testing::internal::identity_t<GMOCK_PP_IF(GMOCK_PP_IS_BEGIN_PARENS(_Ret), \
|
||||||
|
GMOCK_PP_REMOVE_PARENS, \
|
||||||
|
GMOCK_PP_IDENTITY)(_Ret)>( \
|
||||||
|
GMOCK_PP_FOR_EACH(GMOCK_INTERNAL_GET_TYPE, _, _Args))
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_GET_TYPE(_i, _, _elem) \
|
||||||
|
GMOCK_PP_COMMA_IF(_i) \
|
||||||
|
GMOCK_PP_IF(GMOCK_PP_IS_BEGIN_PARENS(_elem), GMOCK_PP_REMOVE_PARENS, \
|
||||||
|
GMOCK_PP_IDENTITY) \
|
||||||
|
(_elem)
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_PARAMETER(_i, _Signature, _) \
|
||||||
|
GMOCK_PP_COMMA_IF(_i) \
|
||||||
|
GMOCK_INTERNAL_ARG_O(_i, GMOCK_PP_REMOVE_PARENS(_Signature)) \
|
||||||
|
gmock_a##_i
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_FORWARD_ARG(_i, _Signature, _) \
|
||||||
|
GMOCK_PP_COMMA_IF(_i) \
|
||||||
|
::std::forward<GMOCK_INTERNAL_ARG_O( \
|
||||||
|
_i, GMOCK_PP_REMOVE_PARENS(_Signature))>(gmock_a##_i)
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_MATCHER_PARAMETER(_i, _Signature, _) \
|
||||||
|
GMOCK_PP_COMMA_IF(_i) \
|
||||||
|
GMOCK_INTERNAL_MATCHER_O(_i, GMOCK_PP_REMOVE_PARENS(_Signature)) \
|
||||||
|
gmock_a##_i
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_MATCHER_ARGUMENT(_i, _1, _2) \
|
||||||
|
GMOCK_PP_COMMA_IF(_i) \
|
||||||
|
gmock_a##_i
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_A_MATCHER_ARGUMENT(_i, _Signature, _) \
|
||||||
|
GMOCK_PP_COMMA_IF(_i) \
|
||||||
|
::testing::A<GMOCK_INTERNAL_ARG_O(_i, GMOCK_PP_REMOVE_PARENS(_Signature))>()
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_ARG_O(_i, ...) \
|
||||||
|
typename ::testing::internal::Function<__VA_ARGS__>::template Arg<_i>::type
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_MATCHER_O(_i, ...) \
|
||||||
|
const ::testing::Matcher<typename ::testing::internal::Function< \
|
||||||
|
__VA_ARGS__>::template Arg<_i>::type>&
|
||||||
|
|
||||||
|
#define MOCK_METHOD0(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 0, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD1(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 1, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD2(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 2, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD3(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 3, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD4(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 4, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD5(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 5, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD6(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 6, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD7(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 7, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD8(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 8, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD9(m, ...) GMOCK_INTERNAL_MOCK_METHODN(, , m, 9, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD10(m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(, , m, 10, __VA_ARGS__)
|
||||||
|
|
||||||
|
#define MOCK_CONST_METHOD0(m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, , m, 0, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD1(m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, , m, 1, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD2(m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, , m, 2, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD3(m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, , m, 3, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD4(m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, , m, 4, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD5(m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, , m, 5, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD6(m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, , m, 6, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD7(m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, , m, 7, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD8(m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, , m, 8, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD9(m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, , m, 9, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD10(m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, , m, 10, __VA_ARGS__)
|
||||||
|
|
||||||
|
#define MOCK_METHOD0_T(m, ...) MOCK_METHOD0(m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD1_T(m, ...) MOCK_METHOD1(m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD2_T(m, ...) MOCK_METHOD2(m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD3_T(m, ...) MOCK_METHOD3(m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD4_T(m, ...) MOCK_METHOD4(m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD5_T(m, ...) MOCK_METHOD5(m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD6_T(m, ...) MOCK_METHOD6(m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD7_T(m, ...) MOCK_METHOD7(m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD8_T(m, ...) MOCK_METHOD8(m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD9_T(m, ...) MOCK_METHOD9(m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD10_T(m, ...) MOCK_METHOD10(m, __VA_ARGS__)
|
||||||
|
|
||||||
|
#define MOCK_CONST_METHOD0_T(m, ...) MOCK_CONST_METHOD0(m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD1_T(m, ...) MOCK_CONST_METHOD1(m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD2_T(m, ...) MOCK_CONST_METHOD2(m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD3_T(m, ...) MOCK_CONST_METHOD3(m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD4_T(m, ...) MOCK_CONST_METHOD4(m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD5_T(m, ...) MOCK_CONST_METHOD5(m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD6_T(m, ...) MOCK_CONST_METHOD6(m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD7_T(m, ...) MOCK_CONST_METHOD7(m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD8_T(m, ...) MOCK_CONST_METHOD8(m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD9_T(m, ...) MOCK_CONST_METHOD9(m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD10_T(m, ...) MOCK_CONST_METHOD10(m, __VA_ARGS__)
|
||||||
|
|
||||||
|
#define MOCK_METHOD0_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 0, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD1_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 1, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD2_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 2, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD3_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 3, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD4_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 4, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD5_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 5, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD6_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 6, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD7_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 7, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD8_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 8, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD9_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 9, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD10_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(, ct, m, 10, __VA_ARGS__)
|
||||||
|
|
||||||
|
#define MOCK_CONST_METHOD0_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 0, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD1_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 1, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD2_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 2, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD3_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 3, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD4_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 4, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD5_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 5, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD6_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 6, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD7_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 7, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD8_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 8, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD9_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 9, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD10_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHODN(const, ct, m, 10, __VA_ARGS__)
|
||||||
|
|
||||||
|
#define MOCK_METHOD0_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_METHOD0_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD1_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_METHOD1_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD2_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_METHOD2_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD3_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_METHOD3_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD4_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_METHOD4_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD5_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_METHOD5_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD6_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_METHOD6_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD7_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_METHOD7_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD8_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_METHOD8_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD9_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_METHOD9_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_METHOD10_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_METHOD10_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
|
||||||
|
#define MOCK_CONST_METHOD0_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_CONST_METHOD0_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD1_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_CONST_METHOD1_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD2_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_CONST_METHOD2_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD3_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_CONST_METHOD3_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD4_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_CONST_METHOD4_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD5_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_CONST_METHOD5_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD6_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_CONST_METHOD6_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD7_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_CONST_METHOD7_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD8_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_CONST_METHOD8_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD9_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_CONST_METHOD9_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
#define MOCK_CONST_METHOD10_T_WITH_CALLTYPE(ct, m, ...) \
|
||||||
|
MOCK_CONST_METHOD10_WITH_CALLTYPE(ct, m, __VA_ARGS__)
|
||||||
|
|
||||||
|
#define GMOCK_INTERNAL_MOCK_METHODN(constness, ct, Method, args_num, ...) \
|
||||||
|
GMOCK_INTERNAL_ASSERT_VALID_SIGNATURE( \
|
||||||
|
args_num, ::testing::internal::identity_t<__VA_ARGS__>); \
|
||||||
|
GMOCK_INTERNAL_MOCK_METHOD_IMPL( \
|
||||||
|
args_num, Method, GMOCK_PP_NARG0(constness), 0, 0, , ct, , \
|
||||||
|
(::testing::internal::identity_t<__VA_ARGS__>))
|
||||||
|
|
||||||
|
#define GMOCK_MOCKER_(arity, constness, Method) \
|
||||||
|
GTEST_CONCAT_TOKEN_(gmock##constness##arity##_##Method##_, __LINE__)
|
||||||
|
|
||||||
|
#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_FUNCTION_MOCKER_H_
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,658 @@
|
||||||
|
// Copyright 2007, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Google Mock - a framework for writing C++ mock classes.
|
||||||
|
//
|
||||||
|
// This file implements some commonly used variadic actions.
|
||||||
|
|
||||||
|
// IWYU pragma: private, include "gmock/gmock.h"
|
||||||
|
// IWYU pragma: friend gmock/.*
|
||||||
|
|
||||||
|
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MORE_ACTIONS_H_
|
||||||
|
#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MORE_ACTIONS_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "gmock/gmock-actions.h"
|
||||||
|
#include "gmock/internal/gmock-port.h"
|
||||||
|
|
||||||
|
// Include any custom callback actions added by the local installation.
|
||||||
|
#include "gmock/internal/custom/gmock-generated-actions.h"
|
||||||
|
|
||||||
|
// Sometimes you want to give an action explicit template parameters
|
||||||
|
// that cannot be inferred from its value parameters. ACTION() and
|
||||||
|
// ACTION_P*() don't support that. ACTION_TEMPLATE() remedies that
|
||||||
|
// and can be viewed as an extension to ACTION() and ACTION_P*().
|
||||||
|
//
|
||||||
|
// The syntax:
|
||||||
|
//
|
||||||
|
// ACTION_TEMPLATE(ActionName,
|
||||||
|
// HAS_m_TEMPLATE_PARAMS(kind1, name1, ..., kind_m, name_m),
|
||||||
|
// AND_n_VALUE_PARAMS(p1, ..., p_n)) { statements; }
|
||||||
|
//
|
||||||
|
// defines an action template that takes m explicit template
|
||||||
|
// parameters and n value parameters. name_i is the name of the i-th
|
||||||
|
// template parameter, and kind_i specifies whether it's a typename,
|
||||||
|
// an integral constant, or a template. p_i is the name of the i-th
|
||||||
|
// value parameter.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// // DuplicateArg<k, T>(output) converts the k-th argument of the mock
|
||||||
|
// // function to type T and copies it to *output.
|
||||||
|
// ACTION_TEMPLATE(DuplicateArg,
|
||||||
|
// HAS_2_TEMPLATE_PARAMS(int, k, typename, T),
|
||||||
|
// AND_1_VALUE_PARAMS(output)) {
|
||||||
|
// *output = T(::std::get<k>(args));
|
||||||
|
// }
|
||||||
|
// ...
|
||||||
|
// int n;
|
||||||
|
// EXPECT_CALL(mock, Foo(_, _))
|
||||||
|
// .WillOnce(DuplicateArg<1, unsigned char>(&n));
|
||||||
|
//
|
||||||
|
// To create an instance of an action template, write:
|
||||||
|
//
|
||||||
|
// ActionName<t1, ..., t_m>(v1, ..., v_n)
|
||||||
|
//
|
||||||
|
// where the ts are the template arguments and the vs are the value
|
||||||
|
// arguments. The value argument types are inferred by the compiler.
|
||||||
|
// If you want to explicitly specify the value argument types, you can
|
||||||
|
// provide additional template arguments:
|
||||||
|
//
|
||||||
|
// ActionName<t1, ..., t_m, u1, ..., u_k>(v1, ..., v_n)
|
||||||
|
//
|
||||||
|
// where u_i is the desired type of v_i.
|
||||||
|
//
|
||||||
|
// ACTION_TEMPLATE and ACTION/ACTION_P* can be overloaded on the
|
||||||
|
// number of value parameters, but not on the number of template
|
||||||
|
// parameters. Without the restriction, the meaning of the following
|
||||||
|
// is unclear:
|
||||||
|
//
|
||||||
|
// OverloadedAction<int, bool>(x);
|
||||||
|
//
|
||||||
|
// Are we using a single-template-parameter action where 'bool' refers
|
||||||
|
// to the type of x, or are we using a two-template-parameter action
|
||||||
|
// where the compiler is asked to infer the type of x?
|
||||||
|
//
|
||||||
|
// Implementation notes:
|
||||||
|
//
|
||||||
|
// GMOCK_INTERNAL_*_HAS_m_TEMPLATE_PARAMS and
|
||||||
|
// GMOCK_INTERNAL_*_AND_n_VALUE_PARAMS are internal macros for
|
||||||
|
// implementing ACTION_TEMPLATE. The main trick we use is to create
|
||||||
|
// new macro invocations when expanding a macro. For example, we have
|
||||||
|
//
|
||||||
|
// #define ACTION_TEMPLATE(name, template_params, value_params)
|
||||||
|
// ... GMOCK_INTERNAL_DECL_##template_params ...
|
||||||
|
//
|
||||||
|
// which causes ACTION_TEMPLATE(..., HAS_1_TEMPLATE_PARAMS(typename, T), ...)
|
||||||
|
// to expand to
|
||||||
|
//
|
||||||
|
// ... GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS(typename, T) ...
|
||||||
|
//
|
||||||
|
// Since GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS is a macro, the
|
||||||
|
// preprocessor will continue to expand it to
|
||||||
|
//
|
||||||
|
// ... typename T ...
|
||||||
|
//
|
||||||
|
// This technique conforms to the C++ standard and is portable. It
|
||||||
|
// allows us to implement action templates using O(N) code, where N is
|
||||||
|
// the maximum number of template/value parameters supported. Without
|
||||||
|
// using it, we'd have to devote O(N^2) amount of code to implement all
|
||||||
|
// combinations of m and n.
|
||||||
|
|
||||||
|
// Declares the template parameters.
|
||||||
|
#define GMOCK_INTERNAL_DECL_HAS_1_TEMPLATE_PARAMS(kind0, name0) kind0 name0
|
||||||
|
#define GMOCK_INTERNAL_DECL_HAS_2_TEMPLATE_PARAMS(kind0, name0, kind1, name1) \
|
||||||
|
kind0 name0, kind1 name1
|
||||||
|
#define GMOCK_INTERNAL_DECL_HAS_3_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
|
||||||
|
kind2, name2) \
|
||||||
|
kind0 name0, kind1 name1, kind2 name2
|
||||||
|
#define GMOCK_INTERNAL_DECL_HAS_4_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
|
||||||
|
kind2, name2, kind3, name3) \
|
||||||
|
kind0 name0, kind1 name1, kind2 name2, kind3 name3
|
||||||
|
#define GMOCK_INTERNAL_DECL_HAS_5_TEMPLATE_PARAMS( \
|
||||||
|
kind0, name0, kind1, name1, kind2, name2, kind3, name3, kind4, name4) \
|
||||||
|
kind0 name0, kind1 name1, kind2 name2, kind3 name3, kind4 name4
|
||||||
|
#define GMOCK_INTERNAL_DECL_HAS_6_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
|
||||||
|
kind2, name2, kind3, name3, \
|
||||||
|
kind4, name4, kind5, name5) \
|
||||||
|
kind0 name0, kind1 name1, kind2 name2, kind3 name3, kind4 name4, kind5 name5
|
||||||
|
#define GMOCK_INTERNAL_DECL_HAS_7_TEMPLATE_PARAMS( \
|
||||||
|
kind0, name0, kind1, name1, kind2, name2, kind3, name3, kind4, name4, \
|
||||||
|
kind5, name5, kind6, name6) \
|
||||||
|
kind0 name0, kind1 name1, kind2 name2, kind3 name3, kind4 name4, \
|
||||||
|
kind5 name5, kind6 name6
|
||||||
|
#define GMOCK_INTERNAL_DECL_HAS_8_TEMPLATE_PARAMS( \
|
||||||
|
kind0, name0, kind1, name1, kind2, name2, kind3, name3, kind4, name4, \
|
||||||
|
kind5, name5, kind6, name6, kind7, name7) \
|
||||||
|
kind0 name0, kind1 name1, kind2 name2, kind3 name3, kind4 name4, \
|
||||||
|
kind5 name5, kind6 name6, kind7 name7
|
||||||
|
#define GMOCK_INTERNAL_DECL_HAS_9_TEMPLATE_PARAMS( \
|
||||||
|
kind0, name0, kind1, name1, kind2, name2, kind3, name3, kind4, name4, \
|
||||||
|
kind5, name5, kind6, name6, kind7, name7, kind8, name8) \
|
||||||
|
kind0 name0, kind1 name1, kind2 name2, kind3 name3, kind4 name4, \
|
||||||
|
kind5 name5, kind6 name6, kind7 name7, kind8 name8
|
||||||
|
#define GMOCK_INTERNAL_DECL_HAS_10_TEMPLATE_PARAMS( \
|
||||||
|
kind0, name0, kind1, name1, kind2, name2, kind3, name3, kind4, name4, \
|
||||||
|
kind5, name5, kind6, name6, kind7, name7, kind8, name8, kind9, name9) \
|
||||||
|
kind0 name0, kind1 name1, kind2 name2, kind3 name3, kind4 name4, \
|
||||||
|
kind5 name5, kind6 name6, kind7 name7, kind8 name8, kind9 name9
|
||||||
|
|
||||||
|
// Lists the template parameters.
|
||||||
|
#define GMOCK_INTERNAL_LIST_HAS_1_TEMPLATE_PARAMS(kind0, name0) name0
|
||||||
|
#define GMOCK_INTERNAL_LIST_HAS_2_TEMPLATE_PARAMS(kind0, name0, kind1, name1) \
|
||||||
|
name0, name1
|
||||||
|
#define GMOCK_INTERNAL_LIST_HAS_3_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
|
||||||
|
kind2, name2) \
|
||||||
|
name0, name1, name2
|
||||||
|
#define GMOCK_INTERNAL_LIST_HAS_4_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
|
||||||
|
kind2, name2, kind3, name3) \
|
||||||
|
name0, name1, name2, name3
|
||||||
|
#define GMOCK_INTERNAL_LIST_HAS_5_TEMPLATE_PARAMS( \
|
||||||
|
kind0, name0, kind1, name1, kind2, name2, kind3, name3, kind4, name4) \
|
||||||
|
name0, name1, name2, name3, name4
|
||||||
|
#define GMOCK_INTERNAL_LIST_HAS_6_TEMPLATE_PARAMS(kind0, name0, kind1, name1, \
|
||||||
|
kind2, name2, kind3, name3, \
|
||||||
|
kind4, name4, kind5, name5) \
|
||||||
|
name0, name1, name2, name3, name4, name5
|
||||||
|
#define GMOCK_INTERNAL_LIST_HAS_7_TEMPLATE_PARAMS( \
|
||||||
|
kind0, name0, kind1, name1, kind2, name2, kind3, name3, kind4, name4, \
|
||||||
|
kind5, name5, kind6, name6) \
|
||||||
|
name0, name1, name2, name3, name4, name5, name6
|
||||||
|
#define GMOCK_INTERNAL_LIST_HAS_8_TEMPLATE_PARAMS( \
|
||||||
|
kind0, name0, kind1, name1, kind2, name2, kind3, name3, kind4, name4, \
|
||||||
|
kind5, name5, kind6, name6, kind7, name7) \
|
||||||
|
name0, name1, name2, name3, name4, name5, name6, name7
|
||||||
|
#define GMOCK_INTERNAL_LIST_HAS_9_TEMPLATE_PARAMS( \
|
||||||
|
kind0, name0, kind1, name1, kind2, name2, kind3, name3, kind4, name4, \
|
||||||
|
kind5, name5, kind6, name6, kind7, name7, kind8, name8) \
|
||||||
|
name0, name1, name2, name3, name4, name5, name6, name7, name8
|
||||||
|
#define GMOCK_INTERNAL_LIST_HAS_10_TEMPLATE_PARAMS( \
|
||||||
|
kind0, name0, kind1, name1, kind2, name2, kind3, name3, kind4, name4, \
|
||||||
|
kind5, name5, kind6, name6, kind7, name7, kind8, name8, kind9, name9) \
|
||||||
|
name0, name1, name2, name3, name4, name5, name6, name7, name8, name9
|
||||||
|
|
||||||
|
// Declares the types of value parameters.
|
||||||
|
#define GMOCK_INTERNAL_DECL_TYPE_AND_0_VALUE_PARAMS()
|
||||||
|
#define GMOCK_INTERNAL_DECL_TYPE_AND_1_VALUE_PARAMS(p0) , typename p0##_type
|
||||||
|
#define GMOCK_INTERNAL_DECL_TYPE_AND_2_VALUE_PARAMS(p0, p1) \
|
||||||
|
, typename p0##_type, typename p1##_type
|
||||||
|
#define GMOCK_INTERNAL_DECL_TYPE_AND_3_VALUE_PARAMS(p0, p1, p2) \
|
||||||
|
, typename p0##_type, typename p1##_type, typename p2##_type
|
||||||
|
#define GMOCK_INTERNAL_DECL_TYPE_AND_4_VALUE_PARAMS(p0, p1, p2, p3) \
|
||||||
|
, typename p0##_type, typename p1##_type, typename p2##_type, \
|
||||||
|
typename p3##_type
|
||||||
|
#define GMOCK_INTERNAL_DECL_TYPE_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) \
|
||||||
|
, typename p0##_type, typename p1##_type, typename p2##_type, \
|
||||||
|
typename p3##_type, typename p4##_type
|
||||||
|
#define GMOCK_INTERNAL_DECL_TYPE_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) \
|
||||||
|
, typename p0##_type, typename p1##_type, typename p2##_type, \
|
||||||
|
typename p3##_type, typename p4##_type, typename p5##_type
|
||||||
|
#define GMOCK_INTERNAL_DECL_TYPE_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \
|
||||||
|
p6) \
|
||||||
|
, typename p0##_type, typename p1##_type, typename p2##_type, \
|
||||||
|
typename p3##_type, typename p4##_type, typename p5##_type, \
|
||||||
|
typename p6##_type
|
||||||
|
#define GMOCK_INTERNAL_DECL_TYPE_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \
|
||||||
|
p6, p7) \
|
||||||
|
, typename p0##_type, typename p1##_type, typename p2##_type, \
|
||||||
|
typename p3##_type, typename p4##_type, typename p5##_type, \
|
||||||
|
typename p6##_type, typename p7##_type
|
||||||
|
#define GMOCK_INTERNAL_DECL_TYPE_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \
|
||||||
|
p6, p7, p8) \
|
||||||
|
, typename p0##_type, typename p1##_type, typename p2##_type, \
|
||||||
|
typename p3##_type, typename p4##_type, typename p5##_type, \
|
||||||
|
typename p6##_type, typename p7##_type, typename p8##_type
|
||||||
|
#define GMOCK_INTERNAL_DECL_TYPE_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \
|
||||||
|
p6, p7, p8, p9) \
|
||||||
|
, typename p0##_type, typename p1##_type, typename p2##_type, \
|
||||||
|
typename p3##_type, typename p4##_type, typename p5##_type, \
|
||||||
|
typename p6##_type, typename p7##_type, typename p8##_type, \
|
||||||
|
typename p9##_type
|
||||||
|
|
||||||
|
// Initializes the value parameters.
|
||||||
|
#define GMOCK_INTERNAL_INIT_AND_0_VALUE_PARAMS() ()
|
||||||
|
#define GMOCK_INTERNAL_INIT_AND_1_VALUE_PARAMS(p0) \
|
||||||
|
(p0##_type gmock_p0) : p0(::std::move(gmock_p0))
|
||||||
|
#define GMOCK_INTERNAL_INIT_AND_2_VALUE_PARAMS(p0, p1) \
|
||||||
|
(p0##_type gmock_p0, p1##_type gmock_p1) \
|
||||||
|
: p0(::std::move(gmock_p0)), p1(::std::move(gmock_p1))
|
||||||
|
#define GMOCK_INTERNAL_INIT_AND_3_VALUE_PARAMS(p0, p1, p2) \
|
||||||
|
(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2) \
|
||||||
|
: p0(::std::move(gmock_p0)), \
|
||||||
|
p1(::std::move(gmock_p1)), \
|
||||||
|
p2(::std::move(gmock_p2))
|
||||||
|
#define GMOCK_INTERNAL_INIT_AND_4_VALUE_PARAMS(p0, p1, p2, p3) \
|
||||||
|
(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \
|
||||||
|
p3##_type gmock_p3) \
|
||||||
|
: p0(::std::move(gmock_p0)), \
|
||||||
|
p1(::std::move(gmock_p1)), \
|
||||||
|
p2(::std::move(gmock_p2)), \
|
||||||
|
p3(::std::move(gmock_p3))
|
||||||
|
#define GMOCK_INTERNAL_INIT_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) \
|
||||||
|
(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \
|
||||||
|
p3##_type gmock_p3, p4##_type gmock_p4) \
|
||||||
|
: p0(::std::move(gmock_p0)), \
|
||||||
|
p1(::std::move(gmock_p1)), \
|
||||||
|
p2(::std::move(gmock_p2)), \
|
||||||
|
p3(::std::move(gmock_p3)), \
|
||||||
|
p4(::std::move(gmock_p4))
|
||||||
|
#define GMOCK_INTERNAL_INIT_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) \
|
||||||
|
(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \
|
||||||
|
p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5) \
|
||||||
|
: p0(::std::move(gmock_p0)), \
|
||||||
|
p1(::std::move(gmock_p1)), \
|
||||||
|
p2(::std::move(gmock_p2)), \
|
||||||
|
p3(::std::move(gmock_p3)), \
|
||||||
|
p4(::std::move(gmock_p4)), \
|
||||||
|
p5(::std::move(gmock_p5))
|
||||||
|
#define GMOCK_INTERNAL_INIT_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6) \
|
||||||
|
(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \
|
||||||
|
p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \
|
||||||
|
p6##_type gmock_p6) \
|
||||||
|
: p0(::std::move(gmock_p0)), \
|
||||||
|
p1(::std::move(gmock_p1)), \
|
||||||
|
p2(::std::move(gmock_p2)), \
|
||||||
|
p3(::std::move(gmock_p3)), \
|
||||||
|
p4(::std::move(gmock_p4)), \
|
||||||
|
p5(::std::move(gmock_p5)), \
|
||||||
|
p6(::std::move(gmock_p6))
|
||||||
|
#define GMOCK_INTERNAL_INIT_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7) \
|
||||||
|
(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \
|
||||||
|
p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \
|
||||||
|
p6##_type gmock_p6, p7##_type gmock_p7) \
|
||||||
|
: p0(::std::move(gmock_p0)), \
|
||||||
|
p1(::std::move(gmock_p1)), \
|
||||||
|
p2(::std::move(gmock_p2)), \
|
||||||
|
p3(::std::move(gmock_p3)), \
|
||||||
|
p4(::std::move(gmock_p4)), \
|
||||||
|
p5(::std::move(gmock_p5)), \
|
||||||
|
p6(::std::move(gmock_p6)), \
|
||||||
|
p7(::std::move(gmock_p7))
|
||||||
|
#define GMOCK_INTERNAL_INIT_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7, \
|
||||||
|
p8) \
|
||||||
|
(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \
|
||||||
|
p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \
|
||||||
|
p6##_type gmock_p6, p7##_type gmock_p7, p8##_type gmock_p8) \
|
||||||
|
: p0(::std::move(gmock_p0)), \
|
||||||
|
p1(::std::move(gmock_p1)), \
|
||||||
|
p2(::std::move(gmock_p2)), \
|
||||||
|
p3(::std::move(gmock_p3)), \
|
||||||
|
p4(::std::move(gmock_p4)), \
|
||||||
|
p5(::std::move(gmock_p5)), \
|
||||||
|
p6(::std::move(gmock_p6)), \
|
||||||
|
p7(::std::move(gmock_p7)), \
|
||||||
|
p8(::std::move(gmock_p8))
|
||||||
|
#define GMOCK_INTERNAL_INIT_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
|
||||||
|
p7, p8, p9) \
|
||||||
|
(p0##_type gmock_p0, p1##_type gmock_p1, p2##_type gmock_p2, \
|
||||||
|
p3##_type gmock_p3, p4##_type gmock_p4, p5##_type gmock_p5, \
|
||||||
|
p6##_type gmock_p6, p7##_type gmock_p7, p8##_type gmock_p8, \
|
||||||
|
p9##_type gmock_p9) \
|
||||||
|
: p0(::std::move(gmock_p0)), \
|
||||||
|
p1(::std::move(gmock_p1)), \
|
||||||
|
p2(::std::move(gmock_p2)), \
|
||||||
|
p3(::std::move(gmock_p3)), \
|
||||||
|
p4(::std::move(gmock_p4)), \
|
||||||
|
p5(::std::move(gmock_p5)), \
|
||||||
|
p6(::std::move(gmock_p6)), \
|
||||||
|
p7(::std::move(gmock_p7)), \
|
||||||
|
p8(::std::move(gmock_p8)), \
|
||||||
|
p9(::std::move(gmock_p9))
|
||||||
|
|
||||||
|
// Defines the copy constructor
|
||||||
|
#define GMOCK_INTERNAL_DEFN_COPY_AND_0_VALUE_PARAMS() \
|
||||||
|
{} // Avoid https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82134
|
||||||
|
#define GMOCK_INTERNAL_DEFN_COPY_AND_1_VALUE_PARAMS(...) = default;
|
||||||
|
#define GMOCK_INTERNAL_DEFN_COPY_AND_2_VALUE_PARAMS(...) = default;
|
||||||
|
#define GMOCK_INTERNAL_DEFN_COPY_AND_3_VALUE_PARAMS(...) = default;
|
||||||
|
#define GMOCK_INTERNAL_DEFN_COPY_AND_4_VALUE_PARAMS(...) = default;
|
||||||
|
#define GMOCK_INTERNAL_DEFN_COPY_AND_5_VALUE_PARAMS(...) = default;
|
||||||
|
#define GMOCK_INTERNAL_DEFN_COPY_AND_6_VALUE_PARAMS(...) = default;
|
||||||
|
#define GMOCK_INTERNAL_DEFN_COPY_AND_7_VALUE_PARAMS(...) = default;
|
||||||
|
#define GMOCK_INTERNAL_DEFN_COPY_AND_8_VALUE_PARAMS(...) = default;
|
||||||
|
#define GMOCK_INTERNAL_DEFN_COPY_AND_9_VALUE_PARAMS(...) = default;
|
||||||
|
#define GMOCK_INTERNAL_DEFN_COPY_AND_10_VALUE_PARAMS(...) = default;
|
||||||
|
|
||||||
|
// Declares the fields for storing the value parameters.
|
||||||
|
#define GMOCK_INTERNAL_DEFN_AND_0_VALUE_PARAMS()
|
||||||
|
#define GMOCK_INTERNAL_DEFN_AND_1_VALUE_PARAMS(p0) p0##_type p0;
|
||||||
|
#define GMOCK_INTERNAL_DEFN_AND_2_VALUE_PARAMS(p0, p1) \
|
||||||
|
p0##_type p0; \
|
||||||
|
p1##_type p1;
|
||||||
|
#define GMOCK_INTERNAL_DEFN_AND_3_VALUE_PARAMS(p0, p1, p2) \
|
||||||
|
p0##_type p0; \
|
||||||
|
p1##_type p1; \
|
||||||
|
p2##_type p2;
|
||||||
|
#define GMOCK_INTERNAL_DEFN_AND_4_VALUE_PARAMS(p0, p1, p2, p3) \
|
||||||
|
p0##_type p0; \
|
||||||
|
p1##_type p1; \
|
||||||
|
p2##_type p2; \
|
||||||
|
p3##_type p3;
|
||||||
|
#define GMOCK_INTERNAL_DEFN_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) \
|
||||||
|
p0##_type p0; \
|
||||||
|
p1##_type p1; \
|
||||||
|
p2##_type p2; \
|
||||||
|
p3##_type p3; \
|
||||||
|
p4##_type p4;
|
||||||
|
#define GMOCK_INTERNAL_DEFN_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) \
|
||||||
|
p0##_type p0; \
|
||||||
|
p1##_type p1; \
|
||||||
|
p2##_type p2; \
|
||||||
|
p3##_type p3; \
|
||||||
|
p4##_type p4; \
|
||||||
|
p5##_type p5;
|
||||||
|
#define GMOCK_INTERNAL_DEFN_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6) \
|
||||||
|
p0##_type p0; \
|
||||||
|
p1##_type p1; \
|
||||||
|
p2##_type p2; \
|
||||||
|
p3##_type p3; \
|
||||||
|
p4##_type p4; \
|
||||||
|
p5##_type p5; \
|
||||||
|
p6##_type p6;
|
||||||
|
#define GMOCK_INTERNAL_DEFN_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7) \
|
||||||
|
p0##_type p0; \
|
||||||
|
p1##_type p1; \
|
||||||
|
p2##_type p2; \
|
||||||
|
p3##_type p3; \
|
||||||
|
p4##_type p4; \
|
||||||
|
p5##_type p5; \
|
||||||
|
p6##_type p6; \
|
||||||
|
p7##_type p7;
|
||||||
|
#define GMOCK_INTERNAL_DEFN_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7, \
|
||||||
|
p8) \
|
||||||
|
p0##_type p0; \
|
||||||
|
p1##_type p1; \
|
||||||
|
p2##_type p2; \
|
||||||
|
p3##_type p3; \
|
||||||
|
p4##_type p4; \
|
||||||
|
p5##_type p5; \
|
||||||
|
p6##_type p6; \
|
||||||
|
p7##_type p7; \
|
||||||
|
p8##_type p8;
|
||||||
|
#define GMOCK_INTERNAL_DEFN_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
|
||||||
|
p7, p8, p9) \
|
||||||
|
p0##_type p0; \
|
||||||
|
p1##_type p1; \
|
||||||
|
p2##_type p2; \
|
||||||
|
p3##_type p3; \
|
||||||
|
p4##_type p4; \
|
||||||
|
p5##_type p5; \
|
||||||
|
p6##_type p6; \
|
||||||
|
p7##_type p7; \
|
||||||
|
p8##_type p8; \
|
||||||
|
p9##_type p9;
|
||||||
|
|
||||||
|
// Lists the value parameters.
|
||||||
|
#define GMOCK_INTERNAL_LIST_AND_0_VALUE_PARAMS()
|
||||||
|
#define GMOCK_INTERNAL_LIST_AND_1_VALUE_PARAMS(p0) p0
|
||||||
|
#define GMOCK_INTERNAL_LIST_AND_2_VALUE_PARAMS(p0, p1) p0, p1
|
||||||
|
#define GMOCK_INTERNAL_LIST_AND_3_VALUE_PARAMS(p0, p1, p2) p0, p1, p2
|
||||||
|
#define GMOCK_INTERNAL_LIST_AND_4_VALUE_PARAMS(p0, p1, p2, p3) p0, p1, p2, p3
|
||||||
|
#define GMOCK_INTERNAL_LIST_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) \
|
||||||
|
p0, p1, p2, p3, p4
|
||||||
|
#define GMOCK_INTERNAL_LIST_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) \
|
||||||
|
p0, p1, p2, p3, p4, p5
|
||||||
|
#define GMOCK_INTERNAL_LIST_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6) \
|
||||||
|
p0, p1, p2, p3, p4, p5, p6
|
||||||
|
#define GMOCK_INTERNAL_LIST_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7) \
|
||||||
|
p0, p1, p2, p3, p4, p5, p6, p7
|
||||||
|
#define GMOCK_INTERNAL_LIST_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7, \
|
||||||
|
p8) \
|
||||||
|
p0, p1, p2, p3, p4, p5, p6, p7, p8
|
||||||
|
#define GMOCK_INTERNAL_LIST_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
|
||||||
|
p7, p8, p9) \
|
||||||
|
p0, p1, p2, p3, p4, p5, p6, p7, p8, p9
|
||||||
|
|
||||||
|
// Lists the value parameter types.
|
||||||
|
#define GMOCK_INTERNAL_LIST_TYPE_AND_0_VALUE_PARAMS()
|
||||||
|
#define GMOCK_INTERNAL_LIST_TYPE_AND_1_VALUE_PARAMS(p0) , p0##_type
|
||||||
|
#define GMOCK_INTERNAL_LIST_TYPE_AND_2_VALUE_PARAMS(p0, p1) \
|
||||||
|
, p0##_type, p1##_type
|
||||||
|
#define GMOCK_INTERNAL_LIST_TYPE_AND_3_VALUE_PARAMS(p0, p1, p2) \
|
||||||
|
, p0##_type, p1##_type, p2##_type
|
||||||
|
#define GMOCK_INTERNAL_LIST_TYPE_AND_4_VALUE_PARAMS(p0, p1, p2, p3) \
|
||||||
|
, p0##_type, p1##_type, p2##_type, p3##_type
|
||||||
|
#define GMOCK_INTERNAL_LIST_TYPE_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) \
|
||||||
|
, p0##_type, p1##_type, p2##_type, p3##_type, p4##_type
|
||||||
|
#define GMOCK_INTERNAL_LIST_TYPE_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) \
|
||||||
|
, p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, p5##_type
|
||||||
|
#define GMOCK_INTERNAL_LIST_TYPE_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \
|
||||||
|
p6) \
|
||||||
|
, p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, p5##_type, p6##_type
|
||||||
|
#define GMOCK_INTERNAL_LIST_TYPE_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \
|
||||||
|
p6, p7) \
|
||||||
|
, p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, p5##_type, \
|
||||||
|
p6##_type, p7##_type
|
||||||
|
#define GMOCK_INTERNAL_LIST_TYPE_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \
|
||||||
|
p6, p7, p8) \
|
||||||
|
, p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, p5##_type, \
|
||||||
|
p6##_type, p7##_type, p8##_type
|
||||||
|
#define GMOCK_INTERNAL_LIST_TYPE_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, \
|
||||||
|
p6, p7, p8, p9) \
|
||||||
|
, p0##_type, p1##_type, p2##_type, p3##_type, p4##_type, p5##_type, \
|
||||||
|
p6##_type, p7##_type, p8##_type, p9##_type
|
||||||
|
|
||||||
|
// Declares the value parameters.
|
||||||
|
#define GMOCK_INTERNAL_DECL_AND_0_VALUE_PARAMS()
|
||||||
|
#define GMOCK_INTERNAL_DECL_AND_1_VALUE_PARAMS(p0) p0##_type p0
|
||||||
|
#define GMOCK_INTERNAL_DECL_AND_2_VALUE_PARAMS(p0, p1) \
|
||||||
|
p0##_type p0, p1##_type p1
|
||||||
|
#define GMOCK_INTERNAL_DECL_AND_3_VALUE_PARAMS(p0, p1, p2) \
|
||||||
|
p0##_type p0, p1##_type p1, p2##_type p2
|
||||||
|
#define GMOCK_INTERNAL_DECL_AND_4_VALUE_PARAMS(p0, p1, p2, p3) \
|
||||||
|
p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3
|
||||||
|
#define GMOCK_INTERNAL_DECL_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) \
|
||||||
|
p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4
|
||||||
|
#define GMOCK_INTERNAL_DECL_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) \
|
||||||
|
p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \
|
||||||
|
p5##_type p5
|
||||||
|
#define GMOCK_INTERNAL_DECL_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6) \
|
||||||
|
p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \
|
||||||
|
p5##_type p5, p6##_type p6
|
||||||
|
#define GMOCK_INTERNAL_DECL_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7) \
|
||||||
|
p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \
|
||||||
|
p5##_type p5, p6##_type p6, p7##_type p7
|
||||||
|
#define GMOCK_INTERNAL_DECL_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, p7, \
|
||||||
|
p8) \
|
||||||
|
p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \
|
||||||
|
p5##_type p5, p6##_type p6, p7##_type p7, p8##_type p8
|
||||||
|
#define GMOCK_INTERNAL_DECL_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
|
||||||
|
p7, p8, p9) \
|
||||||
|
p0##_type p0, p1##_type p1, p2##_type p2, p3##_type p3, p4##_type p4, \
|
||||||
|
p5##_type p5, p6##_type p6, p7##_type p7, p8##_type p8, p9##_type p9
|
||||||
|
|
||||||
|
// The suffix of the class template implementing the action template.
|
||||||
|
#define GMOCK_INTERNAL_COUNT_AND_0_VALUE_PARAMS()
|
||||||
|
#define GMOCK_INTERNAL_COUNT_AND_1_VALUE_PARAMS(p0) P
|
||||||
|
#define GMOCK_INTERNAL_COUNT_AND_2_VALUE_PARAMS(p0, p1) P2
|
||||||
|
#define GMOCK_INTERNAL_COUNT_AND_3_VALUE_PARAMS(p0, p1, p2) P3
|
||||||
|
#define GMOCK_INTERNAL_COUNT_AND_4_VALUE_PARAMS(p0, p1, p2, p3) P4
|
||||||
|
#define GMOCK_INTERNAL_COUNT_AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4) P5
|
||||||
|
#define GMOCK_INTERNAL_COUNT_AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5) P6
|
||||||
|
#define GMOCK_INTERNAL_COUNT_AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6) P7
|
||||||
|
#define GMOCK_INTERNAL_COUNT_AND_8_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
|
||||||
|
p7) \
|
||||||
|
P8
|
||||||
|
#define GMOCK_INTERNAL_COUNT_AND_9_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
|
||||||
|
p7, p8) \
|
||||||
|
P9
|
||||||
|
#define GMOCK_INTERNAL_COUNT_AND_10_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6, \
|
||||||
|
p7, p8, p9) \
|
||||||
|
P10
|
||||||
|
|
||||||
|
// The name of the class template implementing the action template.
|
||||||
|
#define GMOCK_ACTION_CLASS_(name, value_params) \
|
||||||
|
GTEST_CONCAT_TOKEN_(name##Action, GMOCK_INTERNAL_COUNT_##value_params)
|
||||||
|
|
||||||
|
#define ACTION_TEMPLATE(name, template_params, value_params) \
|
||||||
|
template <GMOCK_INTERNAL_DECL_##template_params \
|
||||||
|
GMOCK_INTERNAL_DECL_TYPE_##value_params> \
|
||||||
|
class GMOCK_ACTION_CLASS_(name, value_params) { \
|
||||||
|
public: \
|
||||||
|
explicit GMOCK_ACTION_CLASS_(name, value_params)( \
|
||||||
|
GMOCK_INTERNAL_DECL_##value_params) \
|
||||||
|
GMOCK_PP_IF(GMOCK_PP_IS_EMPTY(GMOCK_INTERNAL_COUNT_##value_params), \
|
||||||
|
= default; \
|
||||||
|
, \
|
||||||
|
: impl_(std::make_shared<gmock_Impl>( \
|
||||||
|
GMOCK_INTERNAL_LIST_##value_params)){}) \
|
||||||
|
GMOCK_ACTION_CLASS_(name, value_params)(const GMOCK_ACTION_CLASS_( \
|
||||||
|
name, value_params) &) noexcept GMOCK_INTERNAL_DEFN_COPY_ \
|
||||||
|
##value_params \
|
||||||
|
GMOCK_ACTION_CLASS_(name, value_params)(GMOCK_ACTION_CLASS_( \
|
||||||
|
name, value_params) &&) noexcept GMOCK_INTERNAL_DEFN_COPY_ \
|
||||||
|
##value_params template <typename F> \
|
||||||
|
operator ::testing::Action<F>() const { \
|
||||||
|
return GMOCK_PP_IF( \
|
||||||
|
GMOCK_PP_IS_EMPTY(GMOCK_INTERNAL_COUNT_##value_params), \
|
||||||
|
(::testing::internal::MakeAction<F, gmock_Impl>()), \
|
||||||
|
(::testing::internal::MakeAction<F>(impl_))); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
private: \
|
||||||
|
class gmock_Impl { \
|
||||||
|
public: \
|
||||||
|
explicit gmock_Impl GMOCK_INTERNAL_INIT_##value_params {} \
|
||||||
|
template <typename function_type, typename return_type, \
|
||||||
|
typename args_type, GMOCK_ACTION_TEMPLATE_ARGS_NAMES_> \
|
||||||
|
return_type gmock_PerformImpl(GMOCK_ACTION_ARG_TYPES_AND_NAMES_) const; \
|
||||||
|
GMOCK_INTERNAL_DEFN_##value_params \
|
||||||
|
}; \
|
||||||
|
GMOCK_PP_IF(GMOCK_PP_IS_EMPTY(GMOCK_INTERNAL_COUNT_##value_params), , \
|
||||||
|
std::shared_ptr<const gmock_Impl> impl_;) \
|
||||||
|
}; \
|
||||||
|
template <GMOCK_INTERNAL_DECL_##template_params \
|
||||||
|
GMOCK_INTERNAL_DECL_TYPE_##value_params> \
|
||||||
|
GMOCK_ACTION_CLASS_( \
|
||||||
|
name, value_params)<GMOCK_INTERNAL_LIST_##template_params \
|
||||||
|
GMOCK_INTERNAL_LIST_TYPE_##value_params> \
|
||||||
|
name(GMOCK_INTERNAL_DECL_##value_params) GTEST_MUST_USE_RESULT_; \
|
||||||
|
template <GMOCK_INTERNAL_DECL_##template_params \
|
||||||
|
GMOCK_INTERNAL_DECL_TYPE_##value_params> \
|
||||||
|
inline GMOCK_ACTION_CLASS_( \
|
||||||
|
name, value_params)<GMOCK_INTERNAL_LIST_##template_params \
|
||||||
|
GMOCK_INTERNAL_LIST_TYPE_##value_params> \
|
||||||
|
name(GMOCK_INTERNAL_DECL_##value_params) { \
|
||||||
|
return GMOCK_ACTION_CLASS_( \
|
||||||
|
name, value_params)<GMOCK_INTERNAL_LIST_##template_params \
|
||||||
|
GMOCK_INTERNAL_LIST_TYPE_##value_params>( \
|
||||||
|
GMOCK_INTERNAL_LIST_##value_params); \
|
||||||
|
} \
|
||||||
|
template <GMOCK_INTERNAL_DECL_##template_params \
|
||||||
|
GMOCK_INTERNAL_DECL_TYPE_##value_params> \
|
||||||
|
template <typename function_type, typename return_type, typename args_type, \
|
||||||
|
GMOCK_ACTION_TEMPLATE_ARGS_NAMES_> \
|
||||||
|
return_type GMOCK_ACTION_CLASS_( \
|
||||||
|
name, value_params)<GMOCK_INTERNAL_LIST_##template_params \
|
||||||
|
GMOCK_INTERNAL_LIST_TYPE_##value_params>:: \
|
||||||
|
gmock_Impl::gmock_PerformImpl(GMOCK_ACTION_ARG_TYPES_AND_NAMES_UNUSED_) \
|
||||||
|
const
|
||||||
|
|
||||||
|
namespace testing {
|
||||||
|
|
||||||
|
// The ACTION*() macros trigger warning C4100 (unreferenced formal
|
||||||
|
// parameter) in MSVC with -W4. Unfortunately they cannot be fixed in
|
||||||
|
// the macro definition, as the warnings are generated when the macro
|
||||||
|
// is expanded and macro expansion cannot contain #pragma. Therefore
|
||||||
|
// we suppress them here.
|
||||||
|
GTEST_DISABLE_MSC_WARNINGS_PUSH_(4100)
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
// internal::InvokeArgument - a helper for InvokeArgument action.
|
||||||
|
// The basic overloads are provided here for generic functors.
|
||||||
|
// Overloads for other custom-callables are provided in the
|
||||||
|
// internal/custom/gmock-generated-actions.h header.
|
||||||
|
template <typename F, typename... Args>
|
||||||
|
auto InvokeArgument(F f, Args... args) -> decltype(f(args...)) {
|
||||||
|
return f(args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t index, typename... Params>
|
||||||
|
struct InvokeArgumentAction {
|
||||||
|
template <typename... Args,
|
||||||
|
typename = typename std::enable_if<(index < sizeof...(Args))>::type>
|
||||||
|
auto operator()(Args &&...args) const -> decltype(internal::InvokeArgument(
|
||||||
|
std::get<index>(std::forward_as_tuple(std::forward<Args>(args)...)),
|
||||||
|
std::declval<const Params &>()...)) {
|
||||||
|
internal::FlatTuple<Args &&...> args_tuple(FlatTupleConstructTag{},
|
||||||
|
std::forward<Args>(args)...);
|
||||||
|
return params.Apply([&](const Params &...unpacked_params) {
|
||||||
|
auto &&callable = args_tuple.template Get<index>();
|
||||||
|
return internal::InvokeArgument(
|
||||||
|
std::forward<decltype(callable)>(callable), unpacked_params...);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
internal::FlatTuple<Params...> params;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
// The InvokeArgument<N>(a1, a2, ..., a_k) action invokes the N-th
|
||||||
|
// (0-based) argument, which must be a k-ary callable, of the mock
|
||||||
|
// function, with arguments a1, a2, ..., a_k.
|
||||||
|
//
|
||||||
|
// Notes:
|
||||||
|
//
|
||||||
|
// 1. The arguments are passed by value by default. If you need to
|
||||||
|
// pass an argument by reference, wrap it inside std::ref(). For
|
||||||
|
// example,
|
||||||
|
//
|
||||||
|
// InvokeArgument<1>(5, string("Hello"), std::ref(foo))
|
||||||
|
//
|
||||||
|
// passes 5 and string("Hello") by value, and passes foo by
|
||||||
|
// reference.
|
||||||
|
//
|
||||||
|
// 2. If the callable takes an argument by reference but std::ref() is
|
||||||
|
// not used, it will receive the reference to a copy of the value,
|
||||||
|
// instead of the original value. For example, when the 0-th
|
||||||
|
// argument of the mock function takes a const string&, the action
|
||||||
|
//
|
||||||
|
// InvokeArgument<0>(string("Hello"))
|
||||||
|
//
|
||||||
|
// makes a copy of the temporary string("Hello") object and passes a
|
||||||
|
// reference of the copy, instead of the original temporary object,
|
||||||
|
// to the callable. This makes it easy for a user to define an
|
||||||
|
// InvokeArgument action from temporary values and have it performed
|
||||||
|
// later.
|
||||||
|
template <std::size_t index, typename... Params>
|
||||||
|
internal::InvokeArgumentAction<index, typename std::decay<Params>::type...>
|
||||||
|
InvokeArgument(Params &&...params) {
|
||||||
|
return {internal::FlatTuple<typename std::decay<Params>::type...>(
|
||||||
|
internal::FlatTupleConstructTag{}, std::forward<Params>(params)...)};
|
||||||
|
}
|
||||||
|
|
||||||
|
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4100
|
||||||
|
|
||||||
|
} // namespace testing
|
||||||
|
|
||||||
|
#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MORE_ACTIONS_H_
|
|
@ -0,0 +1,120 @@
|
||||||
|
// Copyright 2013, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Google Mock - a framework for writing C++ mock classes.
|
||||||
|
//
|
||||||
|
// This file implements some matchers that depend on gmock-matchers.h.
|
||||||
|
//
|
||||||
|
// Note that tests are implemented in gmock-matchers_test.cc rather than
|
||||||
|
// gmock-more-matchers-test.cc.
|
||||||
|
|
||||||
|
// IWYU pragma: private, include "gmock/gmock.h"
|
||||||
|
// IWYU pragma: friend gmock/.*
|
||||||
|
|
||||||
|
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MORE_MATCHERS_H_
|
||||||
|
#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MORE_MATCHERS_H_
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "gmock/gmock-matchers.h"
|
||||||
|
|
||||||
|
namespace testing {
|
||||||
|
|
||||||
|
// Silence C4100 (unreferenced formal
|
||||||
|
// parameter) for MSVC
|
||||||
|
GTEST_DISABLE_MSC_WARNINGS_PUSH_(4100)
|
||||||
|
#if defined(_MSC_VER) && (_MSC_VER == 1900)
|
||||||
|
// and silence C4800 (C4800: 'int *const ': forcing value
|
||||||
|
// to bool 'true' or 'false') for MSVC 14
|
||||||
|
GTEST_DISABLE_MSC_WARNINGS_PUSH_(4800)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
// Implements the polymorphic IsEmpty matcher, which
|
||||||
|
// can be used as a Matcher<T> as long as T is either a container that defines
|
||||||
|
// empty() and size() (e.g. std::vector or std::string), or a C-style string.
|
||||||
|
class IsEmptyMatcher {
|
||||||
|
public:
|
||||||
|
// Matches anything that defines empty() and size().
|
||||||
|
template <typename MatcheeContainerType>
|
||||||
|
bool MatchAndExplain(const MatcheeContainerType& c,
|
||||||
|
MatchResultListener* listener) const {
|
||||||
|
if (c.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
*listener << "whose size is " << c.size();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches C-style strings.
|
||||||
|
bool MatchAndExplain(const char* s, MatchResultListener* listener) const {
|
||||||
|
return MatchAndExplain(std::string(s), listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Describes what this matcher matches.
|
||||||
|
void DescribeTo(std::ostream* os) const { *os << "is empty"; }
|
||||||
|
|
||||||
|
void DescribeNegationTo(std::ostream* os) const { *os << "isn't empty"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
// Creates a polymorphic matcher that matches an empty container or C-style
|
||||||
|
// string. The container must support both size() and empty(), which all
|
||||||
|
// STL-like containers provide.
|
||||||
|
inline PolymorphicMatcher<internal::IsEmptyMatcher> IsEmpty() {
|
||||||
|
return MakePolymorphicMatcher(internal::IsEmptyMatcher());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define a matcher that matches a value that evaluates in boolean
|
||||||
|
// context to true. Useful for types that define "explicit operator
|
||||||
|
// bool" operators and so can't be compared for equality with true
|
||||||
|
// and false.
|
||||||
|
MATCHER(IsTrue, negation ? "is false" : "is true") {
|
||||||
|
return static_cast<bool>(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define a matcher that matches a value that evaluates in boolean
|
||||||
|
// context to false. Useful for types that define "explicit operator
|
||||||
|
// bool" operators and so can't be compared for equality with true
|
||||||
|
// and false.
|
||||||
|
MATCHER(IsFalse, negation ? "is true" : "is false") {
|
||||||
|
return !static_cast<bool>(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(_MSC_VER) && (_MSC_VER == 1900)
|
||||||
|
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4800
|
||||||
|
#endif
|
||||||
|
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4100
|
||||||
|
|
||||||
|
} // namespace testing
|
||||||
|
|
||||||
|
#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_MORE_MATCHERS_H_
|
|
@ -0,0 +1,277 @@
|
||||||
|
// Copyright 2008, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Implements class templates NiceMock, NaggyMock, and StrictMock.
|
||||||
|
//
|
||||||
|
// Given a mock class MockFoo that is created using Google Mock,
|
||||||
|
// NiceMock<MockFoo> is a subclass of MockFoo that allows
|
||||||
|
// uninteresting calls (i.e. calls to mock methods that have no
|
||||||
|
// EXPECT_CALL specs), NaggyMock<MockFoo> is a subclass of MockFoo
|
||||||
|
// that prints a warning when an uninteresting call occurs, and
|
||||||
|
// StrictMock<MockFoo> is a subclass of MockFoo that treats all
|
||||||
|
// uninteresting calls as errors.
|
||||||
|
//
|
||||||
|
// Currently a mock is naggy by default, so MockFoo and
|
||||||
|
// NaggyMock<MockFoo> behave like the same. However, we will soon
|
||||||
|
// switch the default behavior of mocks to be nice, as that in general
|
||||||
|
// leads to more maintainable tests. When that happens, MockFoo will
|
||||||
|
// stop behaving like NaggyMock<MockFoo> and start behaving like
|
||||||
|
// NiceMock<MockFoo>.
|
||||||
|
//
|
||||||
|
// NiceMock, NaggyMock, and StrictMock "inherit" the constructors of
|
||||||
|
// their respective base class. Therefore you can write
|
||||||
|
// NiceMock<MockFoo>(5, "a") to construct a nice mock where MockFoo
|
||||||
|
// has a constructor that accepts (int, const char*), for example.
|
||||||
|
//
|
||||||
|
// A known limitation is that NiceMock<MockFoo>, NaggyMock<MockFoo>,
|
||||||
|
// and StrictMock<MockFoo> only works for mock methods defined using
|
||||||
|
// the MOCK_METHOD* family of macros DIRECTLY in the MockFoo class.
|
||||||
|
// If a mock method is defined in a base class of MockFoo, the "nice"
|
||||||
|
// or "strict" modifier may not affect it, depending on the compiler.
|
||||||
|
// In particular, nesting NiceMock, NaggyMock, and StrictMock is NOT
|
||||||
|
// supported.
|
||||||
|
|
||||||
|
// IWYU pragma: private, include "gmock/gmock.h"
|
||||||
|
// IWYU pragma: friend gmock/.*
|
||||||
|
|
||||||
|
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_NICE_STRICT_H_
|
||||||
|
#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_NICE_STRICT_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "gmock/gmock-spec-builders.h"
|
||||||
|
#include "gmock/internal/gmock-port.h"
|
||||||
|
|
||||||
|
namespace testing {
|
||||||
|
template <class MockClass>
|
||||||
|
class NiceMock;
|
||||||
|
template <class MockClass>
|
||||||
|
class NaggyMock;
|
||||||
|
template <class MockClass>
|
||||||
|
class StrictMock;
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
template <typename T>
|
||||||
|
std::true_type StrictnessModifierProbe(const NiceMock<T>&);
|
||||||
|
template <typename T>
|
||||||
|
std::true_type StrictnessModifierProbe(const NaggyMock<T>&);
|
||||||
|
template <typename T>
|
||||||
|
std::true_type StrictnessModifierProbe(const StrictMock<T>&);
|
||||||
|
std::false_type StrictnessModifierProbe(...);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
constexpr bool HasStrictnessModifier() {
|
||||||
|
return decltype(StrictnessModifierProbe(std::declval<const T&>()))::value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base classes that register and deregister with testing::Mock to alter the
|
||||||
|
// default behavior around uninteresting calls. Inheriting from one of these
|
||||||
|
// classes first and then MockClass ensures the MockClass constructor is run
|
||||||
|
// after registration, and that the MockClass destructor runs before
|
||||||
|
// deregistration. This guarantees that MockClass's constructor and destructor
|
||||||
|
// run with the same level of strictness as its instance methods.
|
||||||
|
|
||||||
|
#if defined(GTEST_OS_WINDOWS) && !defined(GTEST_OS_WINDOWS_MINGW) && \
|
||||||
|
(defined(_MSC_VER) || defined(__clang__))
|
||||||
|
// We need to mark these classes with this declspec to ensure that
|
||||||
|
// the empty base class optimization is performed.
|
||||||
|
#define GTEST_INTERNAL_EMPTY_BASE_CLASS __declspec(empty_bases)
|
||||||
|
#else
|
||||||
|
#define GTEST_INTERNAL_EMPTY_BASE_CLASS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <typename Base>
|
||||||
|
class NiceMockImpl {
|
||||||
|
public:
|
||||||
|
NiceMockImpl() {
|
||||||
|
::testing::Mock::AllowUninterestingCalls(reinterpret_cast<uintptr_t>(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
~NiceMockImpl() {
|
||||||
|
::testing::Mock::UnregisterCallReaction(reinterpret_cast<uintptr_t>(this));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Base>
|
||||||
|
class NaggyMockImpl {
|
||||||
|
public:
|
||||||
|
NaggyMockImpl() {
|
||||||
|
::testing::Mock::WarnUninterestingCalls(reinterpret_cast<uintptr_t>(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
~NaggyMockImpl() {
|
||||||
|
::testing::Mock::UnregisterCallReaction(reinterpret_cast<uintptr_t>(this));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Base>
|
||||||
|
class StrictMockImpl {
|
||||||
|
public:
|
||||||
|
StrictMockImpl() {
|
||||||
|
::testing::Mock::FailUninterestingCalls(reinterpret_cast<uintptr_t>(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
~StrictMockImpl() {
|
||||||
|
::testing::Mock::UnregisterCallReaction(reinterpret_cast<uintptr_t>(this));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
template <class MockClass>
|
||||||
|
class GTEST_INTERNAL_EMPTY_BASE_CLASS NiceMock
|
||||||
|
: private internal::NiceMockImpl<MockClass>,
|
||||||
|
public MockClass {
|
||||||
|
public:
|
||||||
|
static_assert(!internal::HasStrictnessModifier<MockClass>(),
|
||||||
|
"Can't apply NiceMock to a class hierarchy that already has a "
|
||||||
|
"strictness modifier. See "
|
||||||
|
"https://google.github.io/googletest/"
|
||||||
|
"gmock_cook_book.html#NiceStrictNaggy");
|
||||||
|
NiceMock() : MockClass() {
|
||||||
|
static_assert(sizeof(*this) == sizeof(MockClass),
|
||||||
|
"The impl subclass shouldn't introduce any padding");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ideally, we would inherit base class's constructors through a using
|
||||||
|
// declaration, which would preserve their visibility. However, many existing
|
||||||
|
// tests rely on the fact that current implementation reexports protected
|
||||||
|
// constructors as public. These tests would need to be cleaned up first.
|
||||||
|
|
||||||
|
// Single argument constructor is special-cased so that it can be
|
||||||
|
// made explicit.
|
||||||
|
template <typename A>
|
||||||
|
explicit NiceMock(A&& arg) : MockClass(std::forward<A>(arg)) {
|
||||||
|
static_assert(sizeof(*this) == sizeof(MockClass),
|
||||||
|
"The impl subclass shouldn't introduce any padding");
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TArg1, typename TArg2, typename... An>
|
||||||
|
NiceMock(TArg1&& arg1, TArg2&& arg2, An&&... args)
|
||||||
|
: MockClass(std::forward<TArg1>(arg1), std::forward<TArg2>(arg2),
|
||||||
|
std::forward<An>(args)...) {
|
||||||
|
static_assert(sizeof(*this) == sizeof(MockClass),
|
||||||
|
"The impl subclass shouldn't introduce any padding");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
NiceMock(const NiceMock&) = delete;
|
||||||
|
NiceMock& operator=(const NiceMock&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class MockClass>
|
||||||
|
class GTEST_INTERNAL_EMPTY_BASE_CLASS NaggyMock
|
||||||
|
: private internal::NaggyMockImpl<MockClass>,
|
||||||
|
public MockClass {
|
||||||
|
static_assert(!internal::HasStrictnessModifier<MockClass>(),
|
||||||
|
"Can't apply NaggyMock to a class hierarchy that already has a "
|
||||||
|
"strictness modifier. See "
|
||||||
|
"https://google.github.io/googletest/"
|
||||||
|
"gmock_cook_book.html#NiceStrictNaggy");
|
||||||
|
|
||||||
|
public:
|
||||||
|
NaggyMock() : MockClass() {
|
||||||
|
static_assert(sizeof(*this) == sizeof(MockClass),
|
||||||
|
"The impl subclass shouldn't introduce any padding");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ideally, we would inherit base class's constructors through a using
|
||||||
|
// declaration, which would preserve their visibility. However, many existing
|
||||||
|
// tests rely on the fact that current implementation reexports protected
|
||||||
|
// constructors as public. These tests would need to be cleaned up first.
|
||||||
|
|
||||||
|
// Single argument constructor is special-cased so that it can be
|
||||||
|
// made explicit.
|
||||||
|
template <typename A>
|
||||||
|
explicit NaggyMock(A&& arg) : MockClass(std::forward<A>(arg)) {
|
||||||
|
static_assert(sizeof(*this) == sizeof(MockClass),
|
||||||
|
"The impl subclass shouldn't introduce any padding");
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TArg1, typename TArg2, typename... An>
|
||||||
|
NaggyMock(TArg1&& arg1, TArg2&& arg2, An&&... args)
|
||||||
|
: MockClass(std::forward<TArg1>(arg1), std::forward<TArg2>(arg2),
|
||||||
|
std::forward<An>(args)...) {
|
||||||
|
static_assert(sizeof(*this) == sizeof(MockClass),
|
||||||
|
"The impl subclass shouldn't introduce any padding");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
NaggyMock(const NaggyMock&) = delete;
|
||||||
|
NaggyMock& operator=(const NaggyMock&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class MockClass>
|
||||||
|
class GTEST_INTERNAL_EMPTY_BASE_CLASS StrictMock
|
||||||
|
: private internal::StrictMockImpl<MockClass>,
|
||||||
|
public MockClass {
|
||||||
|
public:
|
||||||
|
static_assert(
|
||||||
|
!internal::HasStrictnessModifier<MockClass>(),
|
||||||
|
"Can't apply StrictMock to a class hierarchy that already has a "
|
||||||
|
"strictness modifier. See "
|
||||||
|
"https://google.github.io/googletest/"
|
||||||
|
"gmock_cook_book.html#NiceStrictNaggy");
|
||||||
|
StrictMock() : MockClass() {
|
||||||
|
static_assert(sizeof(*this) == sizeof(MockClass),
|
||||||
|
"The impl subclass shouldn't introduce any padding");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ideally, we would inherit base class's constructors through a using
|
||||||
|
// declaration, which would preserve their visibility. However, many existing
|
||||||
|
// tests rely on the fact that current implementation reexports protected
|
||||||
|
// constructors as public. These tests would need to be cleaned up first.
|
||||||
|
|
||||||
|
// Single argument constructor is special-cased so that it can be
|
||||||
|
// made explicit.
|
||||||
|
template <typename A>
|
||||||
|
explicit StrictMock(A&& arg) : MockClass(std::forward<A>(arg)) {
|
||||||
|
static_assert(sizeof(*this) == sizeof(MockClass),
|
||||||
|
"The impl subclass shouldn't introduce any padding");
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TArg1, typename TArg2, typename... An>
|
||||||
|
StrictMock(TArg1&& arg1, TArg2&& arg2, An&&... args)
|
||||||
|
: MockClass(std::forward<TArg1>(arg1), std::forward<TArg2>(arg2),
|
||||||
|
std::forward<An>(args)...) {
|
||||||
|
static_assert(sizeof(*this) == sizeof(MockClass),
|
||||||
|
"The impl subclass shouldn't introduce any padding");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
StrictMock(const StrictMock&) = delete;
|
||||||
|
StrictMock& operator=(const StrictMock&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
#undef GTEST_INTERNAL_EMPTY_BASE_CLASS
|
||||||
|
|
||||||
|
} // namespace testing
|
||||||
|
|
||||||
|
#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_NICE_STRICT_H_
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,96 @@
|
||||||
|
// Copyright 2007, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Google Mock - a framework for writing C++ mock classes.
|
||||||
|
//
|
||||||
|
// This is the main header file a user should include.
|
||||||
|
|
||||||
|
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_H_
|
||||||
|
#define GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_H_
|
||||||
|
|
||||||
|
// This file implements the following syntax:
|
||||||
|
//
|
||||||
|
// ON_CALL(mock_object, Method(...))
|
||||||
|
// .With(...) ?
|
||||||
|
// .WillByDefault(...);
|
||||||
|
//
|
||||||
|
// where With() is optional and WillByDefault() must appear exactly
|
||||||
|
// once.
|
||||||
|
//
|
||||||
|
// EXPECT_CALL(mock_object, Method(...))
|
||||||
|
// .With(...) ?
|
||||||
|
// .Times(...) ?
|
||||||
|
// .InSequence(...) *
|
||||||
|
// .WillOnce(...) *
|
||||||
|
// .WillRepeatedly(...) ?
|
||||||
|
// .RetiresOnSaturation() ? ;
|
||||||
|
//
|
||||||
|
// where all clauses are optional and WillOnce() can be repeated.
|
||||||
|
|
||||||
|
#include "gmock/gmock-actions.h"
|
||||||
|
#include "gmock/gmock-cardinalities.h"
|
||||||
|
#include "gmock/gmock-function-mocker.h"
|
||||||
|
#include "gmock/gmock-matchers.h"
|
||||||
|
#include "gmock/gmock-more-actions.h"
|
||||||
|
#include "gmock/gmock-more-matchers.h"
|
||||||
|
#include "gmock/gmock-nice-strict.h"
|
||||||
|
#include "gmock/internal/gmock-internal-utils.h"
|
||||||
|
#include "gmock/internal/gmock-port.h"
|
||||||
|
|
||||||
|
// Declares Google Mock flags that we want a user to use programmatically.
|
||||||
|
GMOCK_DECLARE_bool_(catch_leaked_mocks);
|
||||||
|
GMOCK_DECLARE_string_(verbose);
|
||||||
|
GMOCK_DECLARE_int32_(default_mock_behavior);
|
||||||
|
|
||||||
|
namespace testing {
|
||||||
|
|
||||||
|
// Initializes Google Mock. This must be called before running the
|
||||||
|
// tests. In particular, it parses the command line for the flags
|
||||||
|
// that Google Mock recognizes. Whenever a Google Mock flag is seen,
|
||||||
|
// it is removed from argv, and *argc is decremented.
|
||||||
|
//
|
||||||
|
// No value is returned. Instead, the Google Mock flag variables are
|
||||||
|
// updated.
|
||||||
|
//
|
||||||
|
// Since Google Test is needed for Google Mock to work, this function
|
||||||
|
// also initializes Google Test and parses its flags, if that hasn't
|
||||||
|
// been done.
|
||||||
|
GTEST_API_ void InitGoogleMock(int* argc, char** argv);
|
||||||
|
|
||||||
|
// This overloaded version can be used in Windows programs compiled in
|
||||||
|
// UNICODE mode.
|
||||||
|
GTEST_API_ void InitGoogleMock(int* argc, wchar_t** argv);
|
||||||
|
|
||||||
|
// This overloaded version can be used on Arduino/embedded platforms where
|
||||||
|
// there is no argc/argv.
|
||||||
|
GTEST_API_ void InitGoogleMock();
|
||||||
|
|
||||||
|
} // namespace testing
|
||||||
|
|
||||||
|
#endif // GOOGLEMOCK_INCLUDE_GMOCK_GMOCK_H_
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Customization Points
|
||||||
|
|
||||||
|
The custom directory is an injection point for custom user configurations.
|
||||||
|
|
||||||
|
## Header `gmock-port.h`
|
||||||
|
|
||||||
|
The following macros can be defined:
|
||||||
|
|
||||||
|
### Flag related macros:
|
||||||
|
|
||||||
|
* `GMOCK_DECLARE_bool_(name)`
|
||||||
|
* `GMOCK_DECLARE_int32_(name)`
|
||||||
|
* `GMOCK_DECLARE_string_(name)`
|
||||||
|
* `GMOCK_DEFINE_bool_(name, default_val, doc)`
|
||||||
|
* `GMOCK_DEFINE_int32_(name, default_val, doc)`
|
||||||
|
* `GMOCK_DEFINE_string_(name, default_val, doc)`
|
||||||
|
* `GMOCK_FLAG_GET(flag_name)`
|
||||||
|
* `GMOCK_FLAG_SET(flag_name, value)`
|
|
@ -0,0 +1,7 @@
|
||||||
|
// IWYU pragma: private, include "gmock/gmock.h"
|
||||||
|
// IWYU pragma: friend gmock/.*
|
||||||
|
|
||||||
|
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_GENERATED_ACTIONS_H_
|
||||||
|
#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_GENERATED_ACTIONS_H_
|
||||||
|
|
||||||
|
#endif // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_GENERATED_ACTIONS_H_
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2015, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Injection point for custom user configurations. See README for details
|
||||||
|
|
||||||
|
// IWYU pragma: private, include "gmock/gmock.h"
|
||||||
|
// IWYU pragma: friend gmock/.*
|
||||||
|
|
||||||
|
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_MATCHERS_H_
|
||||||
|
#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_MATCHERS_H_
|
||||||
|
#endif // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_MATCHERS_H_
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2009, Google Inc.
|
// Copyright 2015, Google Inc.
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
//
|
//
|
||||||
// Redistribution and use in source and binary forms, with or without
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
@ -26,15 +26,15 @@
|
||||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
//
|
|
||||||
// Author: Josh Kelley (joshkel@gmail.com)
|
|
||||||
//
|
|
||||||
// Google C++ Testing Framework (Google Test)
|
|
||||||
//
|
|
||||||
// Links gtest.lib and gtest_main.lib into the current project in C++Builder.
|
|
||||||
// This means that these libraries can't be renamed, but it's the only way to
|
|
||||||
// ensure that Debug versus Release test builds are linked against the
|
|
||||||
// appropriate Debug or Release build of the libraries.
|
|
||||||
|
|
||||||
#pragma link "gtest.lib"
|
// Injection point for custom user configurations. See README for details
|
||||||
#pragma link "gtest_main.lib"
|
//
|
||||||
|
// ** Custom implementation starts here **
|
||||||
|
|
||||||
|
// IWYU pragma: private, include "gmock/gmock.h"
|
||||||
|
// IWYU pragma: friend gmock/.*
|
||||||
|
|
||||||
|
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_PORT_H_
|
||||||
|
#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_PORT_H_
|
||||||
|
|
||||||
|
#endif // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_CUSTOM_GMOCK_PORT_H_
|
|
@ -0,0 +1,487 @@
|
||||||
|
// Copyright 2007, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Google Mock - a framework for writing C++ mock classes.
|
||||||
|
//
|
||||||
|
// This file defines some utilities useful for implementing Google
|
||||||
|
// Mock. They are subject to change without notice, so please DO NOT
|
||||||
|
// USE THEM IN USER CODE.
|
||||||
|
|
||||||
|
// IWYU pragma: private, include "gmock/gmock.h"
|
||||||
|
// IWYU pragma: friend gmock/.*
|
||||||
|
|
||||||
|
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_INTERNAL_UTILS_H_
|
||||||
|
#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_INTERNAL_UTILS_H_
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <ostream> // NOLINT
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "gmock/internal/gmock-port.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
namespace testing {
|
||||||
|
|
||||||
|
template <typename>
|
||||||
|
class Matcher;
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
// Silence MSVC C4100 (unreferenced formal parameter) and
|
||||||
|
// C4805('==': unsafe mix of type 'const int' and type 'const bool')
|
||||||
|
GTEST_DISABLE_MSC_WARNINGS_PUSH_(4100 4805)
|
||||||
|
|
||||||
|
// Joins a vector of strings as if they are fields of a tuple; returns
|
||||||
|
// the joined string.
|
||||||
|
GTEST_API_ std::string JoinAsKeyValueTuple(
|
||||||
|
const std::vector<const char*>& names, const Strings& values);
|
||||||
|
|
||||||
|
// Converts an identifier name to a space-separated list of lower-case
|
||||||
|
// words. Each maximum substring of the form [A-Za-z][a-z]*|\d+ is
|
||||||
|
// treated as one word. For example, both "FooBar123" and
|
||||||
|
// "foo_bar_123" are converted to "foo bar 123".
|
||||||
|
GTEST_API_ std::string ConvertIdentifierNameToWords(const char* id_name);
|
||||||
|
|
||||||
|
// GetRawPointer(p) returns the raw pointer underlying p when p is a
|
||||||
|
// smart pointer, or returns p itself when p is already a raw pointer.
|
||||||
|
// The following default implementation is for the smart pointer case.
|
||||||
|
template <typename Pointer>
|
||||||
|
inline const typename Pointer::element_type* GetRawPointer(const Pointer& p) {
|
||||||
|
return p.get();
|
||||||
|
}
|
||||||
|
// This overload version is for std::reference_wrapper, which does not work with
|
||||||
|
// the overload above, as it does not have an `element_type`.
|
||||||
|
template <typename Element>
|
||||||
|
inline const Element* GetRawPointer(const std::reference_wrapper<Element>& r) {
|
||||||
|
return &r.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This overloaded version is for the raw pointer case.
|
||||||
|
template <typename Element>
|
||||||
|
inline Element* GetRawPointer(Element* p) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default definitions for all compilers.
|
||||||
|
// NOTE: If you implement support for other compilers, make sure to avoid
|
||||||
|
// unexpected overlaps.
|
||||||
|
// (e.g., Clang also processes #pragma GCC, and clang-cl also handles _MSC_VER.)
|
||||||
|
#define GMOCK_INTERNAL_WARNING_PUSH()
|
||||||
|
#define GMOCK_INTERNAL_WARNING_CLANG(Level, Name)
|
||||||
|
#define GMOCK_INTERNAL_WARNING_POP()
|
||||||
|
|
||||||
|
#if defined(__clang__)
|
||||||
|
#undef GMOCK_INTERNAL_WARNING_PUSH
|
||||||
|
#define GMOCK_INTERNAL_WARNING_PUSH() _Pragma("clang diagnostic push")
|
||||||
|
#undef GMOCK_INTERNAL_WARNING_CLANG
|
||||||
|
#define GMOCK_INTERNAL_WARNING_CLANG(Level, Warning) \
|
||||||
|
_Pragma(GMOCK_PP_INTERNAL_STRINGIZE(clang diagnostic Level Warning))
|
||||||
|
#undef GMOCK_INTERNAL_WARNING_POP
|
||||||
|
#define GMOCK_INTERNAL_WARNING_POP() _Pragma("clang diagnostic pop")
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// MSVC treats wchar_t as a native type usually, but treats it as the
|
||||||
|
// same as unsigned short when the compiler option /Zc:wchar_t- is
|
||||||
|
// specified. It defines _NATIVE_WCHAR_T_DEFINED symbol when wchar_t
|
||||||
|
// is a native type.
|
||||||
|
#if defined(_MSC_VER) && !defined(_NATIVE_WCHAR_T_DEFINED)
|
||||||
|
// wchar_t is a typedef.
|
||||||
|
#else
|
||||||
|
#define GMOCK_WCHAR_T_IS_NATIVE_ 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// In what follows, we use the term "kind" to indicate whether a type
|
||||||
|
// is bool, an integer type (excluding bool), a floating-point type,
|
||||||
|
// or none of them. This categorization is useful for determining
|
||||||
|
// when a matcher argument type can be safely converted to another
|
||||||
|
// type in the implementation of SafeMatcherCast.
|
||||||
|
enum TypeKind { kBool, kInteger, kFloatingPoint, kOther };
|
||||||
|
|
||||||
|
// KindOf<T>::value is the kind of type T.
|
||||||
|
template <typename T>
|
||||||
|
struct KindOf {
|
||||||
|
enum { value = kOther }; // The default kind.
|
||||||
|
};
|
||||||
|
|
||||||
|
// This macro declares that the kind of 'type' is 'kind'.
|
||||||
|
#define GMOCK_DECLARE_KIND_(type, kind) \
|
||||||
|
template <> \
|
||||||
|
struct KindOf<type> { \
|
||||||
|
enum { value = kind }; \
|
||||||
|
}
|
||||||
|
|
||||||
|
GMOCK_DECLARE_KIND_(bool, kBool);
|
||||||
|
|
||||||
|
// All standard integer types.
|
||||||
|
GMOCK_DECLARE_KIND_(char, kInteger);
|
||||||
|
GMOCK_DECLARE_KIND_(signed char, kInteger);
|
||||||
|
GMOCK_DECLARE_KIND_(unsigned char, kInteger);
|
||||||
|
GMOCK_DECLARE_KIND_(short, kInteger); // NOLINT
|
||||||
|
GMOCK_DECLARE_KIND_(unsigned short, kInteger); // NOLINT
|
||||||
|
GMOCK_DECLARE_KIND_(int, kInteger);
|
||||||
|
GMOCK_DECLARE_KIND_(unsigned int, kInteger);
|
||||||
|
GMOCK_DECLARE_KIND_(long, kInteger); // NOLINT
|
||||||
|
GMOCK_DECLARE_KIND_(unsigned long, kInteger); // NOLINT
|
||||||
|
GMOCK_DECLARE_KIND_(long long, kInteger); // NOLINT
|
||||||
|
GMOCK_DECLARE_KIND_(unsigned long long, kInteger); // NOLINT
|
||||||
|
|
||||||
|
#if GMOCK_WCHAR_T_IS_NATIVE_
|
||||||
|
GMOCK_DECLARE_KIND_(wchar_t, kInteger);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// All standard floating-point types.
|
||||||
|
GMOCK_DECLARE_KIND_(float, kFloatingPoint);
|
||||||
|
GMOCK_DECLARE_KIND_(double, kFloatingPoint);
|
||||||
|
GMOCK_DECLARE_KIND_(long double, kFloatingPoint);
|
||||||
|
|
||||||
|
#undef GMOCK_DECLARE_KIND_
|
||||||
|
|
||||||
|
// Evaluates to the kind of 'type'.
|
||||||
|
#define GMOCK_KIND_OF_(type) \
|
||||||
|
static_cast< ::testing::internal::TypeKind>( \
|
||||||
|
::testing::internal::KindOf<type>::value)
|
||||||
|
|
||||||
|
// LosslessArithmeticConvertibleImpl<kFromKind, From, kToKind, To>::value
|
||||||
|
// is true if and only if arithmetic type From can be losslessly converted to
|
||||||
|
// arithmetic type To.
|
||||||
|
//
|
||||||
|
// It's the user's responsibility to ensure that both From and To are
|
||||||
|
// raw (i.e. has no CV modifier, is not a pointer, and is not a
|
||||||
|
// reference) built-in arithmetic types, kFromKind is the kind of
|
||||||
|
// From, and kToKind is the kind of To; the value is
|
||||||
|
// implementation-defined when the above pre-condition is violated.
|
||||||
|
template <TypeKind kFromKind, typename From, TypeKind kToKind, typename To>
|
||||||
|
using LosslessArithmeticConvertibleImpl = std::integral_constant<
|
||||||
|
bool,
|
||||||
|
// clang-format off
|
||||||
|
// Converting from bool is always lossless
|
||||||
|
(kFromKind == kBool) ? true
|
||||||
|
// Converting between any other type kinds will be lossy if the type
|
||||||
|
// kinds are not the same.
|
||||||
|
: (kFromKind != kToKind) ? false
|
||||||
|
: (kFromKind == kInteger &&
|
||||||
|
// Converting between integers of different widths is allowed so long
|
||||||
|
// as the conversion does not go from signed to unsigned.
|
||||||
|
(((sizeof(From) < sizeof(To)) &&
|
||||||
|
!(std::is_signed<From>::value && !std::is_signed<To>::value)) ||
|
||||||
|
// Converting between integers of the same width only requires the
|
||||||
|
// two types to have the same signedness.
|
||||||
|
((sizeof(From) == sizeof(To)) &&
|
||||||
|
(std::is_signed<From>::value == std::is_signed<To>::value)))
|
||||||
|
) ? true
|
||||||
|
// Floating point conversions are lossless if and only if `To` is at least
|
||||||
|
// as wide as `From`.
|
||||||
|
: (kFromKind == kFloatingPoint && (sizeof(From) <= sizeof(To))) ? true
|
||||||
|
: false
|
||||||
|
// clang-format on
|
||||||
|
>;
|
||||||
|
|
||||||
|
// LosslessArithmeticConvertible<From, To>::value is true if and only if
|
||||||
|
// arithmetic type From can be losslessly converted to arithmetic type To.
|
||||||
|
//
|
||||||
|
// It's the user's responsibility to ensure that both From and To are
|
||||||
|
// raw (i.e. has no CV modifier, is not a pointer, and is not a
|
||||||
|
// reference) built-in arithmetic types; the value is
|
||||||
|
// implementation-defined when the above pre-condition is violated.
|
||||||
|
template <typename From, typename To>
|
||||||
|
using LosslessArithmeticConvertible =
|
||||||
|
LosslessArithmeticConvertibleImpl<GMOCK_KIND_OF_(From), From,
|
||||||
|
GMOCK_KIND_OF_(To), To>;
|
||||||
|
|
||||||
|
// This interface knows how to report a Google Mock failure (either
|
||||||
|
// non-fatal or fatal).
|
||||||
|
class FailureReporterInterface {
|
||||||
|
public:
|
||||||
|
// The type of a failure (either non-fatal or fatal).
|
||||||
|
enum FailureType { kNonfatal, kFatal };
|
||||||
|
|
||||||
|
virtual ~FailureReporterInterface() = default;
|
||||||
|
|
||||||
|
// Reports a failure that occurred at the given source file location.
|
||||||
|
virtual void ReportFailure(FailureType type, const char* file, int line,
|
||||||
|
const std::string& message) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns the failure reporter used by Google Mock.
|
||||||
|
GTEST_API_ FailureReporterInterface* GetFailureReporter();
|
||||||
|
|
||||||
|
// Asserts that condition is true; aborts the process with the given
|
||||||
|
// message if condition is false. We cannot use LOG(FATAL) or CHECK()
|
||||||
|
// as Google Mock might be used to mock the log sink itself. We
|
||||||
|
// inline this function to prevent it from showing up in the stack
|
||||||
|
// trace.
|
||||||
|
inline void Assert(bool condition, const char* file, int line,
|
||||||
|
const std::string& msg) {
|
||||||
|
if (!condition) {
|
||||||
|
GetFailureReporter()->ReportFailure(FailureReporterInterface::kFatal, file,
|
||||||
|
line, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline void Assert(bool condition, const char* file, int line) {
|
||||||
|
Assert(condition, file, line, "Assertion failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifies that condition is true; generates a non-fatal failure if
|
||||||
|
// condition is false.
|
||||||
|
inline void Expect(bool condition, const char* file, int line,
|
||||||
|
const std::string& msg) {
|
||||||
|
if (!condition) {
|
||||||
|
GetFailureReporter()->ReportFailure(FailureReporterInterface::kNonfatal,
|
||||||
|
file, line, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline void Expect(bool condition, const char* file, int line) {
|
||||||
|
Expect(condition, file, line, "Expectation failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Severity level of a log.
|
||||||
|
enum LogSeverity { kInfo = 0, kWarning = 1 };
|
||||||
|
|
||||||
|
// Valid values for the --gmock_verbose flag.
|
||||||
|
|
||||||
|
// All logs (informational and warnings) are printed.
|
||||||
|
const char kInfoVerbosity[] = "info";
|
||||||
|
// Only warnings are printed.
|
||||||
|
const char kWarningVerbosity[] = "warning";
|
||||||
|
// No logs are printed.
|
||||||
|
const char kErrorVerbosity[] = "error";
|
||||||
|
|
||||||
|
// Returns true if and only if a log with the given severity is visible
|
||||||
|
// according to the --gmock_verbose flag.
|
||||||
|
GTEST_API_ bool LogIsVisible(LogSeverity severity);
|
||||||
|
|
||||||
|
// Prints the given message to stdout if and only if 'severity' >= the level
|
||||||
|
// specified by the --gmock_verbose flag. If stack_frames_to_skip >=
|
||||||
|
// 0, also prints the stack trace excluding the top
|
||||||
|
// stack_frames_to_skip frames. In opt mode, any positive
|
||||||
|
// stack_frames_to_skip is treated as 0, since we don't know which
|
||||||
|
// function calls will be inlined by the compiler and need to be
|
||||||
|
// conservative.
|
||||||
|
GTEST_API_ void Log(LogSeverity severity, const std::string& message,
|
||||||
|
int stack_frames_to_skip);
|
||||||
|
|
||||||
|
// A marker class that is used to resolve parameterless expectations to the
|
||||||
|
// correct overload. This must not be instantiable, to prevent client code from
|
||||||
|
// accidentally resolving to the overload; for example:
|
||||||
|
//
|
||||||
|
// ON_CALL(mock, Method({}, nullptr))...
|
||||||
|
//
|
||||||
|
class WithoutMatchers {
|
||||||
|
private:
|
||||||
|
WithoutMatchers() {}
|
||||||
|
friend GTEST_API_ WithoutMatchers GetWithoutMatchers();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Internal use only: access the singleton instance of WithoutMatchers.
|
||||||
|
GTEST_API_ WithoutMatchers GetWithoutMatchers();
|
||||||
|
|
||||||
|
// Invalid<T>() is usable as an expression of type T, but will terminate
|
||||||
|
// the program with an assertion failure if actually run. This is useful
|
||||||
|
// when a value of type T is needed for compilation, but the statement
|
||||||
|
// will not really be executed (or we don't care if the statement
|
||||||
|
// crashes).
|
||||||
|
template <typename T>
|
||||||
|
inline T Invalid() {
|
||||||
|
Assert(/*condition=*/false, /*file=*/"", /*line=*/-1,
|
||||||
|
"Internal error: attempt to return invalid value");
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
__builtin_unreachable();
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
__assume(0);
|
||||||
|
#else
|
||||||
|
return Invalid<T>();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given a raw type (i.e. having no top-level reference or const
|
||||||
|
// modifier) RawContainer that's either an STL-style container or a
|
||||||
|
// native array, class StlContainerView<RawContainer> has the
|
||||||
|
// following members:
|
||||||
|
//
|
||||||
|
// - type is a type that provides an STL-style container view to
|
||||||
|
// (i.e. implements the STL container concept for) RawContainer;
|
||||||
|
// - const_reference is a type that provides a reference to a const
|
||||||
|
// RawContainer;
|
||||||
|
// - ConstReference(raw_container) returns a const reference to an STL-style
|
||||||
|
// container view to raw_container, which is a RawContainer.
|
||||||
|
// - Copy(raw_container) returns an STL-style container view of a
|
||||||
|
// copy of raw_container, which is a RawContainer.
|
||||||
|
//
|
||||||
|
// This generic version is used when RawContainer itself is already an
|
||||||
|
// STL-style container.
|
||||||
|
template <class RawContainer>
|
||||||
|
class StlContainerView {
|
||||||
|
public:
|
||||||
|
typedef RawContainer type;
|
||||||
|
typedef const type& const_reference;
|
||||||
|
|
||||||
|
static const_reference ConstReference(const RawContainer& container) {
|
||||||
|
static_assert(!std::is_const<RawContainer>::value,
|
||||||
|
"RawContainer type must not be const");
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
static type Copy(const RawContainer& container) { return container; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// This specialization is used when RawContainer is a native array type.
|
||||||
|
template <typename Element, size_t N>
|
||||||
|
class StlContainerView<Element[N]> {
|
||||||
|
public:
|
||||||
|
typedef typename std::remove_const<Element>::type RawElement;
|
||||||
|
typedef internal::NativeArray<RawElement> type;
|
||||||
|
// NativeArray<T> can represent a native array either by value or by
|
||||||
|
// reference (selected by a constructor argument), so 'const type'
|
||||||
|
// can be used to reference a const native array. We cannot
|
||||||
|
// 'typedef const type& const_reference' here, as that would mean
|
||||||
|
// ConstReference() has to return a reference to a local variable.
|
||||||
|
typedef const type const_reference;
|
||||||
|
|
||||||
|
static const_reference ConstReference(const Element (&array)[N]) {
|
||||||
|
static_assert(std::is_same<Element, RawElement>::value,
|
||||||
|
"Element type must not be const");
|
||||||
|
return type(array, N, RelationToSourceReference());
|
||||||
|
}
|
||||||
|
static type Copy(const Element (&array)[N]) {
|
||||||
|
return type(array, N, RelationToSourceCopy());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This specialization is used when RawContainer is a native array
|
||||||
|
// represented as a (pointer, size) tuple.
|
||||||
|
template <typename ElementPointer, typename Size>
|
||||||
|
class StlContainerView< ::std::tuple<ElementPointer, Size> > {
|
||||||
|
public:
|
||||||
|
typedef typename std::remove_const<
|
||||||
|
typename std::pointer_traits<ElementPointer>::element_type>::type
|
||||||
|
RawElement;
|
||||||
|
typedef internal::NativeArray<RawElement> type;
|
||||||
|
typedef const type const_reference;
|
||||||
|
|
||||||
|
static const_reference ConstReference(
|
||||||
|
const ::std::tuple<ElementPointer, Size>& array) {
|
||||||
|
return type(std::get<0>(array), std::get<1>(array),
|
||||||
|
RelationToSourceReference());
|
||||||
|
}
|
||||||
|
static type Copy(const ::std::tuple<ElementPointer, Size>& array) {
|
||||||
|
return type(std::get<0>(array), std::get<1>(array), RelationToSourceCopy());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// The following specialization prevents the user from instantiating
|
||||||
|
// StlContainer with a reference type.
|
||||||
|
template <typename T>
|
||||||
|
class StlContainerView<T&>;
|
||||||
|
|
||||||
|
// A type transform to remove constness from the first part of a pair.
|
||||||
|
// Pairs like that are used as the value_type of associative containers,
|
||||||
|
// and this transform produces a similar but assignable pair.
|
||||||
|
template <typename T>
|
||||||
|
struct RemoveConstFromKey {
|
||||||
|
typedef T type;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Partially specialized to remove constness from std::pair<const K, V>.
|
||||||
|
template <typename K, typename V>
|
||||||
|
struct RemoveConstFromKey<std::pair<const K, V> > {
|
||||||
|
typedef std::pair<K, V> type;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Emit an assertion failure due to incorrect DoDefault() usage. Out-of-lined to
|
||||||
|
// reduce code size.
|
||||||
|
GTEST_API_ void IllegalDoDefault(const char* file, int line);
|
||||||
|
|
||||||
|
template <typename F, typename Tuple, size_t... Idx>
|
||||||
|
auto ApplyImpl(F&& f, Tuple&& args, IndexSequence<Idx...>)
|
||||||
|
-> decltype(std::forward<F>(f)(
|
||||||
|
std::get<Idx>(std::forward<Tuple>(args))...)) {
|
||||||
|
return std::forward<F>(f)(std::get<Idx>(std::forward<Tuple>(args))...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the function to a tuple of arguments.
|
||||||
|
template <typename F, typename Tuple>
|
||||||
|
auto Apply(F&& f, Tuple&& args) -> decltype(ApplyImpl(
|
||||||
|
std::forward<F>(f), std::forward<Tuple>(args),
|
||||||
|
MakeIndexSequence<std::tuple_size<
|
||||||
|
typename std::remove_reference<Tuple>::type>::value>())) {
|
||||||
|
return ApplyImpl(std::forward<F>(f), std::forward<Tuple>(args),
|
||||||
|
MakeIndexSequence<std::tuple_size<
|
||||||
|
typename std::remove_reference<Tuple>::type>::value>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template struct Function<F>, where F must be a function type, contains
|
||||||
|
// the following typedefs:
|
||||||
|
//
|
||||||
|
// Result: the function's return type.
|
||||||
|
// Arg<N>: the type of the N-th argument, where N starts with 0.
|
||||||
|
// ArgumentTuple: the tuple type consisting of all parameters of F.
|
||||||
|
// ArgumentMatcherTuple: the tuple type consisting of Matchers for all
|
||||||
|
// parameters of F.
|
||||||
|
// MakeResultVoid: the function type obtained by substituting void
|
||||||
|
// for the return type of F.
|
||||||
|
// MakeResultIgnoredValue:
|
||||||
|
// the function type obtained by substituting Something
|
||||||
|
// for the return type of F.
|
||||||
|
template <typename T>
|
||||||
|
struct Function;
|
||||||
|
|
||||||
|
template <typename R, typename... Args>
|
||||||
|
struct Function<R(Args...)> {
|
||||||
|
using Result = R;
|
||||||
|
static constexpr size_t ArgumentCount = sizeof...(Args);
|
||||||
|
template <size_t I>
|
||||||
|
using Arg = ElemFromList<I, Args...>;
|
||||||
|
using ArgumentTuple = std::tuple<Args...>;
|
||||||
|
using ArgumentMatcherTuple = std::tuple<Matcher<Args>...>;
|
||||||
|
using MakeResultVoid = void(Args...);
|
||||||
|
using MakeResultIgnoredValue = IgnoredValue(Args...);
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef GTEST_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL
|
||||||
|
template <typename R, typename... Args>
|
||||||
|
constexpr size_t Function<R(Args...)>::ArgumentCount;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Workaround for MSVC error C2039: 'type': is not a member of 'std'
|
||||||
|
// when std::tuple_element is used.
|
||||||
|
// See: https://github.com/google/googletest/issues/3931
|
||||||
|
// Can be replaced with std::tuple_element_t in C++14.
|
||||||
|
template <size_t I, typename T>
|
||||||
|
using TupleElement = typename std::tuple_element<I, T>::type;
|
||||||
|
|
||||||
|
bool Base64Unescape(const std::string& encoded, std::string* decoded);
|
||||||
|
|
||||||
|
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4100 4805
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
} // namespace testing
|
||||||
|
|
||||||
|
#endif // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_INTERNAL_UTILS_H_
|
|
@ -0,0 +1,139 @@
|
||||||
|
// Copyright 2008, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Low-level types and utilities for porting Google Mock to various
|
||||||
|
// platforms. All macros ending with _ and symbols defined in an
|
||||||
|
// internal namespace are subject to change without notice. Code
|
||||||
|
// outside Google Mock MUST NOT USE THEM DIRECTLY. Macros that don't
|
||||||
|
// end with _ are part of Google Mock's public API and can be used by
|
||||||
|
// code outside Google Mock.
|
||||||
|
|
||||||
|
// IWYU pragma: private, include "gmock/gmock.h"
|
||||||
|
// IWYU pragma: friend gmock/.*
|
||||||
|
|
||||||
|
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PORT_H_
|
||||||
|
#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PORT_H_
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
// Most of the utilities needed for porting Google Mock are also
|
||||||
|
// required for Google Test and are defined in gtest-port.h.
|
||||||
|
//
|
||||||
|
// Note to maintainers: to reduce code duplication, prefer adding
|
||||||
|
// portability utilities to Google Test's gtest-port.h instead of
|
||||||
|
// here, as Google Mock depends on Google Test. Only add a utility
|
||||||
|
// here if it's truly specific to Google Mock.
|
||||||
|
|
||||||
|
#include "gmock/internal/custom/gmock-port.h"
|
||||||
|
#include "gtest/internal/gtest-port.h"
|
||||||
|
|
||||||
|
#ifdef GTEST_HAS_ABSL
|
||||||
|
#include "absl/flags/declare.h"
|
||||||
|
#include "absl/flags/flag.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// For MS Visual C++, check the compiler version. At least VS 2015 is
|
||||||
|
// required to compile Google Mock.
|
||||||
|
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||||
|
#error "At least Visual C++ 2015 (14.0) is required to compile Google Mock."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Macro for referencing flags. This is public as we want the user to
|
||||||
|
// use this syntax to reference Google Mock flags.
|
||||||
|
#define GMOCK_FLAG_NAME_(name) gmock_##name
|
||||||
|
#define GMOCK_FLAG(name) FLAGS_gmock_##name
|
||||||
|
|
||||||
|
// Pick a command line flags implementation.
|
||||||
|
#ifdef GTEST_HAS_ABSL
|
||||||
|
|
||||||
|
// Macros for defining flags.
|
||||||
|
#define GMOCK_DEFINE_bool_(name, default_val, doc) \
|
||||||
|
ABSL_FLAG(bool, GMOCK_FLAG_NAME_(name), default_val, doc)
|
||||||
|
#define GMOCK_DEFINE_int32_(name, default_val, doc) \
|
||||||
|
ABSL_FLAG(int32_t, GMOCK_FLAG_NAME_(name), default_val, doc)
|
||||||
|
#define GMOCK_DEFINE_string_(name, default_val, doc) \
|
||||||
|
ABSL_FLAG(std::string, GMOCK_FLAG_NAME_(name), default_val, doc)
|
||||||
|
|
||||||
|
// Macros for declaring flags.
|
||||||
|
#define GMOCK_DECLARE_bool_(name) \
|
||||||
|
ABSL_DECLARE_FLAG(bool, GMOCK_FLAG_NAME_(name))
|
||||||
|
#define GMOCK_DECLARE_int32_(name) \
|
||||||
|
ABSL_DECLARE_FLAG(int32_t, GMOCK_FLAG_NAME_(name))
|
||||||
|
#define GMOCK_DECLARE_string_(name) \
|
||||||
|
ABSL_DECLARE_FLAG(std::string, GMOCK_FLAG_NAME_(name))
|
||||||
|
|
||||||
|
#define GMOCK_FLAG_GET(name) ::absl::GetFlag(GMOCK_FLAG(name))
|
||||||
|
#define GMOCK_FLAG_SET(name, value) \
|
||||||
|
(void)(::absl::SetFlag(&GMOCK_FLAG(name), value))
|
||||||
|
|
||||||
|
#else // GTEST_HAS_ABSL
|
||||||
|
|
||||||
|
// Macros for defining flags.
|
||||||
|
#define GMOCK_DEFINE_bool_(name, default_val, doc) \
|
||||||
|
namespace testing { \
|
||||||
|
GTEST_API_ bool GMOCK_FLAG(name) = (default_val); \
|
||||||
|
} \
|
||||||
|
static_assert(true, "no-op to require trailing semicolon")
|
||||||
|
#define GMOCK_DEFINE_int32_(name, default_val, doc) \
|
||||||
|
namespace testing { \
|
||||||
|
GTEST_API_ int32_t GMOCK_FLAG(name) = (default_val); \
|
||||||
|
} \
|
||||||
|
static_assert(true, "no-op to require trailing semicolon")
|
||||||
|
#define GMOCK_DEFINE_string_(name, default_val, doc) \
|
||||||
|
namespace testing { \
|
||||||
|
GTEST_API_ ::std::string GMOCK_FLAG(name) = (default_val); \
|
||||||
|
} \
|
||||||
|
static_assert(true, "no-op to require trailing semicolon")
|
||||||
|
|
||||||
|
// Macros for declaring flags.
|
||||||
|
#define GMOCK_DECLARE_bool_(name) \
|
||||||
|
namespace testing { \
|
||||||
|
GTEST_API_ extern bool GMOCK_FLAG(name); \
|
||||||
|
} \
|
||||||
|
static_assert(true, "no-op to require trailing semicolon")
|
||||||
|
#define GMOCK_DECLARE_int32_(name) \
|
||||||
|
namespace testing { \
|
||||||
|
GTEST_API_ extern int32_t GMOCK_FLAG(name); \
|
||||||
|
} \
|
||||||
|
static_assert(true, "no-op to require trailing semicolon")
|
||||||
|
#define GMOCK_DECLARE_string_(name) \
|
||||||
|
namespace testing { \
|
||||||
|
GTEST_API_ extern ::std::string GMOCK_FLAG(name); \
|
||||||
|
} \
|
||||||
|
static_assert(true, "no-op to require trailing semicolon")
|
||||||
|
|
||||||
|
#define GMOCK_FLAG_GET(name) ::testing::GMOCK_FLAG(name)
|
||||||
|
#define GMOCK_FLAG_SET(name, value) (void)(::testing::GMOCK_FLAG(name) = value)
|
||||||
|
|
||||||
|
#endif // GTEST_HAS_ABSL
|
||||||
|
|
||||||
|
#endif // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PORT_H_
|
|
@ -0,0 +1,279 @@
|
||||||
|
#ifndef GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PP_H_
|
||||||
|
#define GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PP_H_
|
||||||
|
|
||||||
|
// Expands and concatenates the arguments. Constructed macros reevaluate.
|
||||||
|
#define GMOCK_PP_CAT(_1, _2) GMOCK_PP_INTERNAL_CAT(_1, _2)
|
||||||
|
|
||||||
|
// Expands and stringifies the only argument.
|
||||||
|
#define GMOCK_PP_STRINGIZE(...) GMOCK_PP_INTERNAL_STRINGIZE(__VA_ARGS__)
|
||||||
|
|
||||||
|
// Returns empty. Given a variadic number of arguments.
|
||||||
|
#define GMOCK_PP_EMPTY(...)
|
||||||
|
|
||||||
|
// Returns a comma. Given a variadic number of arguments.
|
||||||
|
#define GMOCK_PP_COMMA(...) ,
|
||||||
|
|
||||||
|
// Returns the only argument.
|
||||||
|
#define GMOCK_PP_IDENTITY(_1) _1
|
||||||
|
|
||||||
|
// Evaluates to the number of arguments after expansion.
|
||||||
|
//
|
||||||
|
// #define PAIR x, y
|
||||||
|
//
|
||||||
|
// GMOCK_PP_NARG() => 1
|
||||||
|
// GMOCK_PP_NARG(x) => 1
|
||||||
|
// GMOCK_PP_NARG(x, y) => 2
|
||||||
|
// GMOCK_PP_NARG(PAIR) => 2
|
||||||
|
//
|
||||||
|
// Requires: the number of arguments after expansion is at most 15.
|
||||||
|
#define GMOCK_PP_NARG(...) \
|
||||||
|
GMOCK_PP_INTERNAL_16TH( \
|
||||||
|
(__VA_ARGS__, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
|
||||||
|
|
||||||
|
// Returns 1 if the expansion of arguments has an unprotected comma. Otherwise
|
||||||
|
// returns 0. Requires no more than 15 unprotected commas.
|
||||||
|
#define GMOCK_PP_HAS_COMMA(...) \
|
||||||
|
GMOCK_PP_INTERNAL_16TH( \
|
||||||
|
(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0))
|
||||||
|
|
||||||
|
// Returns the first argument.
|
||||||
|
#define GMOCK_PP_HEAD(...) GMOCK_PP_INTERNAL_HEAD((__VA_ARGS__, unusedArg))
|
||||||
|
|
||||||
|
// Returns the tail. A variadic list of all arguments minus the first. Requires
|
||||||
|
// at least one argument.
|
||||||
|
#define GMOCK_PP_TAIL(...) GMOCK_PP_INTERNAL_TAIL((__VA_ARGS__))
|
||||||
|
|
||||||
|
// Calls CAT(_Macro, NARG(__VA_ARGS__))(__VA_ARGS__)
|
||||||
|
#define GMOCK_PP_VARIADIC_CALL(_Macro, ...) \
|
||||||
|
GMOCK_PP_IDENTITY( \
|
||||||
|
GMOCK_PP_CAT(_Macro, GMOCK_PP_NARG(__VA_ARGS__))(__VA_ARGS__))
|
||||||
|
|
||||||
|
// If the arguments after expansion have no tokens, evaluates to `1`. Otherwise
|
||||||
|
// evaluates to `0`.
|
||||||
|
//
|
||||||
|
// Requires: * the number of arguments after expansion is at most 15.
|
||||||
|
// * If the argument is a macro, it must be able to be called with one
|
||||||
|
// argument.
|
||||||
|
//
|
||||||
|
// Implementation details:
|
||||||
|
//
|
||||||
|
// There is one case when it generates a compile error: if the argument is macro
|
||||||
|
// that cannot be called with one argument.
|
||||||
|
//
|
||||||
|
// #define M(a, b) // it doesn't matter what it expands to
|
||||||
|
//
|
||||||
|
// // Expected: expands to `0`.
|
||||||
|
// // Actual: compile error.
|
||||||
|
// GMOCK_PP_IS_EMPTY(M)
|
||||||
|
//
|
||||||
|
// There are 4 cases tested:
|
||||||
|
//
|
||||||
|
// * __VA_ARGS__ possible expansion has no unparen'd commas. Expected 0.
|
||||||
|
// * __VA_ARGS__ possible expansion is not enclosed in parenthesis. Expected 0.
|
||||||
|
// * __VA_ARGS__ possible expansion is not a macro that ()-evaluates to a comma.
|
||||||
|
// Expected 0
|
||||||
|
// * __VA_ARGS__ is empty, or has unparen'd commas, or is enclosed in
|
||||||
|
// parenthesis, or is a macro that ()-evaluates to comma. Expected 1.
|
||||||
|
//
|
||||||
|
// We trigger detection on '0001', i.e. on empty.
|
||||||
|
#define GMOCK_PP_IS_EMPTY(...) \
|
||||||
|
GMOCK_PP_INTERNAL_IS_EMPTY(GMOCK_PP_HAS_COMMA(__VA_ARGS__), \
|
||||||
|
GMOCK_PP_HAS_COMMA(GMOCK_PP_COMMA __VA_ARGS__), \
|
||||||
|
GMOCK_PP_HAS_COMMA(__VA_ARGS__()), \
|
||||||
|
GMOCK_PP_HAS_COMMA(GMOCK_PP_COMMA __VA_ARGS__()))
|
||||||
|
|
||||||
|
// Evaluates to _Then if _Cond is 1 and _Else if _Cond is 0.
|
||||||
|
#define GMOCK_PP_IF(_Cond, _Then, _Else) \
|
||||||
|
GMOCK_PP_CAT(GMOCK_PP_INTERNAL_IF_, _Cond)(_Then, _Else)
|
||||||
|
|
||||||
|
// Similar to GMOCK_PP_IF but takes _Then and _Else in parentheses.
|
||||||
|
//
|
||||||
|
// GMOCK_PP_GENERIC_IF(1, (a, b, c), (d, e, f)) => a, b, c
|
||||||
|
// GMOCK_PP_GENERIC_IF(0, (a, b, c), (d, e, f)) => d, e, f
|
||||||
|
//
|
||||||
|
#define GMOCK_PP_GENERIC_IF(_Cond, _Then, _Else) \
|
||||||
|
GMOCK_PP_REMOVE_PARENS(GMOCK_PP_IF(_Cond, _Then, _Else))
|
||||||
|
|
||||||
|
// Evaluates to the number of arguments after expansion. Identifies 'empty' as
|
||||||
|
// 0.
|
||||||
|
//
|
||||||
|
// #define PAIR x, y
|
||||||
|
//
|
||||||
|
// GMOCK_PP_NARG0() => 0
|
||||||
|
// GMOCK_PP_NARG0(x) => 1
|
||||||
|
// GMOCK_PP_NARG0(x, y) => 2
|
||||||
|
// GMOCK_PP_NARG0(PAIR) => 2
|
||||||
|
//
|
||||||
|
// Requires: * the number of arguments after expansion is at most 15.
|
||||||
|
// * If the argument is a macro, it must be able to be called with one
|
||||||
|
// argument.
|
||||||
|
#define GMOCK_PP_NARG0(...) \
|
||||||
|
GMOCK_PP_IF(GMOCK_PP_IS_EMPTY(__VA_ARGS__), 0, GMOCK_PP_NARG(__VA_ARGS__))
|
||||||
|
|
||||||
|
// Expands to 1 if the first argument starts with something in parentheses,
|
||||||
|
// otherwise to 0.
|
||||||
|
#define GMOCK_PP_IS_BEGIN_PARENS(...) \
|
||||||
|
GMOCK_PP_HEAD(GMOCK_PP_CAT(GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_R_, \
|
||||||
|
GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_C __VA_ARGS__))
|
||||||
|
|
||||||
|
// Expands to 1 is there is only one argument and it is enclosed in parentheses.
|
||||||
|
#define GMOCK_PP_IS_ENCLOSED_PARENS(...) \
|
||||||
|
GMOCK_PP_IF(GMOCK_PP_IS_BEGIN_PARENS(__VA_ARGS__), \
|
||||||
|
GMOCK_PP_IS_EMPTY(GMOCK_PP_EMPTY __VA_ARGS__), 0)
|
||||||
|
|
||||||
|
// Remove the parens, requires GMOCK_PP_IS_ENCLOSED_PARENS(args) => 1.
|
||||||
|
#define GMOCK_PP_REMOVE_PARENS(...) GMOCK_PP_INTERNAL_REMOVE_PARENS __VA_ARGS__
|
||||||
|
|
||||||
|
// Expands to _Macro(0, _Data, e1) _Macro(1, _Data, e2) ... _Macro(K -1, _Data,
|
||||||
|
// eK) as many of GMOCK_INTERNAL_NARG0 _Tuple.
|
||||||
|
// Requires: * |_Macro| can be called with 3 arguments.
|
||||||
|
// * |_Tuple| expansion has no more than 15 elements.
|
||||||
|
#define GMOCK_PP_FOR_EACH(_Macro, _Data, _Tuple) \
|
||||||
|
GMOCK_PP_CAT(GMOCK_PP_INTERNAL_FOR_EACH_IMPL_, GMOCK_PP_NARG0 _Tuple) \
|
||||||
|
(0, _Macro, _Data, _Tuple)
|
||||||
|
|
||||||
|
// Expands to _Macro(0, _Data, ) _Macro(1, _Data, ) ... _Macro(K - 1, _Data, )
|
||||||
|
// Empty if _K = 0.
|
||||||
|
// Requires: * |_Macro| can be called with 3 arguments.
|
||||||
|
// * |_K| literal between 0 and 15
|
||||||
|
#define GMOCK_PP_REPEAT(_Macro, _Data, _N) \
|
||||||
|
GMOCK_PP_CAT(GMOCK_PP_INTERNAL_FOR_EACH_IMPL_, _N) \
|
||||||
|
(0, _Macro, _Data, GMOCK_PP_INTENRAL_EMPTY_TUPLE)
|
||||||
|
|
||||||
|
// Increments the argument, requires the argument to be between 0 and 15.
|
||||||
|
#define GMOCK_PP_INC(_i) GMOCK_PP_CAT(GMOCK_PP_INTERNAL_INC_, _i)
|
||||||
|
|
||||||
|
// Returns comma if _i != 0. Requires _i to be between 0 and 15.
|
||||||
|
#define GMOCK_PP_COMMA_IF(_i) GMOCK_PP_CAT(GMOCK_PP_INTERNAL_COMMA_IF_, _i)
|
||||||
|
|
||||||
|
// Internal details follow. Do not use any of these symbols outside of this
|
||||||
|
// file or we will break your code.
|
||||||
|
#define GMOCK_PP_INTENRAL_EMPTY_TUPLE (, , , , , , , , , , , , , , , )
|
||||||
|
#define GMOCK_PP_INTERNAL_CAT(_1, _2) _1##_2
|
||||||
|
#define GMOCK_PP_INTERNAL_STRINGIZE(...) #__VA_ARGS__
|
||||||
|
#define GMOCK_PP_INTERNAL_CAT_5(_1, _2, _3, _4, _5) _1##_2##_3##_4##_5
|
||||||
|
#define GMOCK_PP_INTERNAL_IS_EMPTY(_1, _2, _3, _4) \
|
||||||
|
GMOCK_PP_HAS_COMMA(GMOCK_PP_INTERNAL_CAT_5(GMOCK_PP_INTERNAL_IS_EMPTY_CASE_, \
|
||||||
|
_1, _2, _3, _4))
|
||||||
|
#define GMOCK_PP_INTERNAL_IS_EMPTY_CASE_0001 ,
|
||||||
|
#define GMOCK_PP_INTERNAL_IF_1(_Then, _Else) _Then
|
||||||
|
#define GMOCK_PP_INTERNAL_IF_0(_Then, _Else) _Else
|
||||||
|
|
||||||
|
// Because of MSVC treating a token with a comma in it as a single token when
|
||||||
|
// passed to another macro, we need to force it to evaluate it as multiple
|
||||||
|
// tokens. We do that by using a "IDENTITY(MACRO PARENTHESIZED_ARGS)" macro. We
|
||||||
|
// define one per possible macro that relies on this behavior. Note "_Args" must
|
||||||
|
// be parenthesized.
|
||||||
|
#define GMOCK_PP_INTERNAL_INTERNAL_16TH(_1, _2, _3, _4, _5, _6, _7, _8, _9, \
|
||||||
|
_10, _11, _12, _13, _14, _15, _16, \
|
||||||
|
...) \
|
||||||
|
_16
|
||||||
|
#define GMOCK_PP_INTERNAL_16TH(_Args) \
|
||||||
|
GMOCK_PP_IDENTITY(GMOCK_PP_INTERNAL_INTERNAL_16TH _Args)
|
||||||
|
#define GMOCK_PP_INTERNAL_INTERNAL_HEAD(_1, ...) _1
|
||||||
|
#define GMOCK_PP_INTERNAL_HEAD(_Args) \
|
||||||
|
GMOCK_PP_IDENTITY(GMOCK_PP_INTERNAL_INTERNAL_HEAD _Args)
|
||||||
|
#define GMOCK_PP_INTERNAL_INTERNAL_TAIL(_1, ...) __VA_ARGS__
|
||||||
|
#define GMOCK_PP_INTERNAL_TAIL(_Args) \
|
||||||
|
GMOCK_PP_IDENTITY(GMOCK_PP_INTERNAL_INTERNAL_TAIL _Args)
|
||||||
|
|
||||||
|
#define GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_C(...) 1 _
|
||||||
|
#define GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_R_1 1,
|
||||||
|
#define GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_R_GMOCK_PP_INTERNAL_IBP_IS_VARIADIC_C \
|
||||||
|
0,
|
||||||
|
#define GMOCK_PP_INTERNAL_REMOVE_PARENS(...) __VA_ARGS__
|
||||||
|
#define GMOCK_PP_INTERNAL_INC_0 1
|
||||||
|
#define GMOCK_PP_INTERNAL_INC_1 2
|
||||||
|
#define GMOCK_PP_INTERNAL_INC_2 3
|
||||||
|
#define GMOCK_PP_INTERNAL_INC_3 4
|
||||||
|
#define GMOCK_PP_INTERNAL_INC_4 5
|
||||||
|
#define GMOCK_PP_INTERNAL_INC_5 6
|
||||||
|
#define GMOCK_PP_INTERNAL_INC_6 7
|
||||||
|
#define GMOCK_PP_INTERNAL_INC_7 8
|
||||||
|
#define GMOCK_PP_INTERNAL_INC_8 9
|
||||||
|
#define GMOCK_PP_INTERNAL_INC_9 10
|
||||||
|
#define GMOCK_PP_INTERNAL_INC_10 11
|
||||||
|
#define GMOCK_PP_INTERNAL_INC_11 12
|
||||||
|
#define GMOCK_PP_INTERNAL_INC_12 13
|
||||||
|
#define GMOCK_PP_INTERNAL_INC_13 14
|
||||||
|
#define GMOCK_PP_INTERNAL_INC_14 15
|
||||||
|
#define GMOCK_PP_INTERNAL_INC_15 16
|
||||||
|
#define GMOCK_PP_INTERNAL_COMMA_IF_0
|
||||||
|
#define GMOCK_PP_INTERNAL_COMMA_IF_1 ,
|
||||||
|
#define GMOCK_PP_INTERNAL_COMMA_IF_2 ,
|
||||||
|
#define GMOCK_PP_INTERNAL_COMMA_IF_3 ,
|
||||||
|
#define GMOCK_PP_INTERNAL_COMMA_IF_4 ,
|
||||||
|
#define GMOCK_PP_INTERNAL_COMMA_IF_5 ,
|
||||||
|
#define GMOCK_PP_INTERNAL_COMMA_IF_6 ,
|
||||||
|
#define GMOCK_PP_INTERNAL_COMMA_IF_7 ,
|
||||||
|
#define GMOCK_PP_INTERNAL_COMMA_IF_8 ,
|
||||||
|
#define GMOCK_PP_INTERNAL_COMMA_IF_9 ,
|
||||||
|
#define GMOCK_PP_INTERNAL_COMMA_IF_10 ,
|
||||||
|
#define GMOCK_PP_INTERNAL_COMMA_IF_11 ,
|
||||||
|
#define GMOCK_PP_INTERNAL_COMMA_IF_12 ,
|
||||||
|
#define GMOCK_PP_INTERNAL_COMMA_IF_13 ,
|
||||||
|
#define GMOCK_PP_INTERNAL_COMMA_IF_14 ,
|
||||||
|
#define GMOCK_PP_INTERNAL_COMMA_IF_15 ,
|
||||||
|
#define GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, _element) \
|
||||||
|
_Macro(_i, _Data, _element)
|
||||||
|
#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_0(_i, _Macro, _Data, _Tuple)
|
||||||
|
#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_1(_i, _Macro, _Data, _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple)
|
||||||
|
#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_2(_i, _Macro, _Data, _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_FOR_EACH_IMPL_1(GMOCK_PP_INC(_i), _Macro, _Data, \
|
||||||
|
(GMOCK_PP_TAIL _Tuple))
|
||||||
|
#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_3(_i, _Macro, _Data, _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_FOR_EACH_IMPL_2(GMOCK_PP_INC(_i), _Macro, _Data, \
|
||||||
|
(GMOCK_PP_TAIL _Tuple))
|
||||||
|
#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_4(_i, _Macro, _Data, _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_FOR_EACH_IMPL_3(GMOCK_PP_INC(_i), _Macro, _Data, \
|
||||||
|
(GMOCK_PP_TAIL _Tuple))
|
||||||
|
#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_5(_i, _Macro, _Data, _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_FOR_EACH_IMPL_4(GMOCK_PP_INC(_i), _Macro, _Data, \
|
||||||
|
(GMOCK_PP_TAIL _Tuple))
|
||||||
|
#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_6(_i, _Macro, _Data, _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_FOR_EACH_IMPL_5(GMOCK_PP_INC(_i), _Macro, _Data, \
|
||||||
|
(GMOCK_PP_TAIL _Tuple))
|
||||||
|
#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_7(_i, _Macro, _Data, _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_FOR_EACH_IMPL_6(GMOCK_PP_INC(_i), _Macro, _Data, \
|
||||||
|
(GMOCK_PP_TAIL _Tuple))
|
||||||
|
#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_8(_i, _Macro, _Data, _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_FOR_EACH_IMPL_7(GMOCK_PP_INC(_i), _Macro, _Data, \
|
||||||
|
(GMOCK_PP_TAIL _Tuple))
|
||||||
|
#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_9(_i, _Macro, _Data, _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_FOR_EACH_IMPL_8(GMOCK_PP_INC(_i), _Macro, _Data, \
|
||||||
|
(GMOCK_PP_TAIL _Tuple))
|
||||||
|
#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_10(_i, _Macro, _Data, _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_FOR_EACH_IMPL_9(GMOCK_PP_INC(_i), _Macro, _Data, \
|
||||||
|
(GMOCK_PP_TAIL _Tuple))
|
||||||
|
#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_11(_i, _Macro, _Data, _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_FOR_EACH_IMPL_10(GMOCK_PP_INC(_i), _Macro, _Data, \
|
||||||
|
(GMOCK_PP_TAIL _Tuple))
|
||||||
|
#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_12(_i, _Macro, _Data, _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_FOR_EACH_IMPL_11(GMOCK_PP_INC(_i), _Macro, _Data, \
|
||||||
|
(GMOCK_PP_TAIL _Tuple))
|
||||||
|
#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_13(_i, _Macro, _Data, _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_FOR_EACH_IMPL_12(GMOCK_PP_INC(_i), _Macro, _Data, \
|
||||||
|
(GMOCK_PP_TAIL _Tuple))
|
||||||
|
#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_14(_i, _Macro, _Data, _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_FOR_EACH_IMPL_13(GMOCK_PP_INC(_i), _Macro, _Data, \
|
||||||
|
(GMOCK_PP_TAIL _Tuple))
|
||||||
|
#define GMOCK_PP_INTERNAL_FOR_EACH_IMPL_15(_i, _Macro, _Data, _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_CALL_MACRO(_Macro, _i, _Data, GMOCK_PP_HEAD _Tuple) \
|
||||||
|
GMOCK_PP_INTERNAL_FOR_EACH_IMPL_14(GMOCK_PP_INC(_i), _Macro, _Data, \
|
||||||
|
(GMOCK_PP_TAIL _Tuple))
|
||||||
|
|
||||||
|
#endif // GOOGLEMOCK_INCLUDE_GMOCK_INTERNAL_GMOCK_PP_H_
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright 2008, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
//
|
||||||
|
// Google C++ Mocking Framework (Google Mock)
|
||||||
|
//
|
||||||
|
// This file #includes all Google Mock implementation .cc files. The
|
||||||
|
// purpose is to allow a user to build Google Mock by compiling this
|
||||||
|
// file alone.
|
||||||
|
|
||||||
|
// This line ensures that gmock.h can be compiled on its own, even
|
||||||
|
// when it's fused.
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
|
||||||
|
// The following lines pull in the real gmock *.cc files.
|
||||||
|
#include "src/gmock-cardinalities.cc"
|
||||||
|
#include "src/gmock-internal-utils.cc"
|
||||||
|
#include "src/gmock-matchers.cc"
|
||||||
|
#include "src/gmock-spec-builders.cc"
|
||||||
|
#include "src/gmock.cc"
|
|
@ -0,0 +1,155 @@
|
||||||
|
// Copyright 2007, Google Inc.
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Google Mock - a framework for writing C++ mock classes.
|
||||||
|
//
|
||||||
|
// This file implements cardinalities.
|
||||||
|
|
||||||
|
#include "gmock/gmock-cardinalities.h"
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#include <ostream> // NOLINT
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "gmock/internal/gmock-internal-utils.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
namespace testing {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Implements the Between(m, n) cardinality.
|
||||||
|
class BetweenCardinalityImpl : public CardinalityInterface {
|
||||||
|
public:
|
||||||
|
BetweenCardinalityImpl(int min, int max)
|
||||||
|
: min_(min >= 0 ? min : 0), max_(max >= min_ ? max : min_) {
|
||||||
|
std::stringstream ss;
|
||||||
|
if (min < 0) {
|
||||||
|
ss << "The invocation lower bound must be >= 0, "
|
||||||
|
<< "but is actually " << min << ".";
|
||||||
|
internal::Expect(false, __FILE__, __LINE__, ss.str());
|
||||||
|
} else if (max < 0) {
|
||||||
|
ss << "The invocation upper bound must be >= 0, "
|
||||||
|
<< "but is actually " << max << ".";
|
||||||
|
internal::Expect(false, __FILE__, __LINE__, ss.str());
|
||||||
|
} else if (min > max) {
|
||||||
|
ss << "The invocation upper bound (" << max
|
||||||
|
<< ") must be >= the invocation lower bound (" << min << ").";
|
||||||
|
internal::Expect(false, __FILE__, __LINE__, ss.str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conservative estimate on the lower/upper bound of the number of
|
||||||
|
// calls allowed.
|
||||||
|
int ConservativeLowerBound() const override { return min_; }
|
||||||
|
int ConservativeUpperBound() const override { return max_; }
|
||||||
|
|
||||||
|
bool IsSatisfiedByCallCount(int call_count) const override {
|
||||||
|
return min_ <= call_count && call_count <= max_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsSaturatedByCallCount(int call_count) const override {
|
||||||
|
return call_count >= max_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DescribeTo(::std::ostream* os) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const int min_;
|
||||||
|
const int max_;
|
||||||
|
|
||||||
|
BetweenCardinalityImpl(const BetweenCardinalityImpl&) = delete;
|
||||||
|
BetweenCardinalityImpl& operator=(const BetweenCardinalityImpl&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Formats "n times" in a human-friendly way.
|
||||||
|
inline std::string FormatTimes(int n) {
|
||||||
|
if (n == 1) {
|
||||||
|
return "once";
|
||||||
|
} else if (n == 2) {
|
||||||
|
return "twice";
|
||||||
|
} else {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << n << " times";
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Describes the Between(m, n) cardinality in human-friendly text.
|
||||||
|
void BetweenCardinalityImpl::DescribeTo(::std::ostream* os) const {
|
||||||
|
if (min_ == 0) {
|
||||||
|
if (max_ == 0) {
|
||||||
|
*os << "never called";
|
||||||
|
} else if (max_ == INT_MAX) {
|
||||||
|
*os << "called any number of times";
|
||||||
|
} else {
|
||||||
|
*os << "called at most " << FormatTimes(max_);
|
||||||
|
}
|
||||||
|
} else if (min_ == max_) {
|
||||||
|
*os << "called " << FormatTimes(min_);
|
||||||
|
} else if (max_ == INT_MAX) {
|
||||||
|
*os << "called at least " << FormatTimes(min_);
|
||||||
|
} else {
|
||||||
|
// 0 < min_ < max_ < INT_MAX
|
||||||
|
*os << "called between " << min_ << " and " << max_ << " times";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Unnamed namespace
|
||||||
|
|
||||||
|
// Describes the given call count to an ostream.
|
||||||
|
void Cardinality::DescribeActualCallCountTo(int actual_call_count,
|
||||||
|
::std::ostream* os) {
|
||||||
|
if (actual_call_count > 0) {
|
||||||
|
*os << "called " << FormatTimes(actual_call_count);
|
||||||
|
} else {
|
||||||
|
*os << "never called";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a cardinality that allows at least n calls.
|
||||||
|
GTEST_API_ Cardinality AtLeast(int n) { return Between(n, INT_MAX); }
|
||||||
|
|
||||||
|
// Creates a cardinality that allows at most n calls.
|
||||||
|
GTEST_API_ Cardinality AtMost(int n) { return Between(0, n); }
|
||||||
|
|
||||||
|
// Creates a cardinality that allows any number of calls.
|
||||||
|
GTEST_API_ Cardinality AnyNumber() { return AtLeast(0); }
|
||||||
|
|
||||||
|
// Creates a cardinality that allows between min and max calls.
|
||||||
|
GTEST_API_ Cardinality Between(int min, int max) {
|
||||||
|
return Cardinality(new BetweenCardinalityImpl(min, max));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a cardinality that allows exactly n calls.
|
||||||
|
GTEST_API_ Cardinality Exactly(int n) { return Between(n, n); }
|
||||||
|
|
||||||
|
} // namespace testing
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue