├── .bazelrc ├── .bazelversion ├── .clang-format ├── .gitignore ├── AUTHORS ├── BUILD.bazel ├── CHANGES ├── CMakeLists.txt ├── CONTRIBUTING.md ├── CONTRIBUTORS ├── DEPS ├── DEVELOPMENT.howto.md ├── LICENSE ├── MODULE.bazel ├── README.md ├── WORKSPACE ├── cmake ├── linux-mingw-toolchain.cmake ├── setup_build.cmake └── utils.cmake ├── effcee ├── CMakeLists.txt ├── check.cc ├── check.h ├── check_test.cc ├── cursor.h ├── cursor_test.cc ├── diagnostic.h ├── diagnostic_test.cc ├── effcee.h ├── make_unique.h ├── match.cc ├── match_test.cc ├── options_test.cc ├── result_test.cc └── to_string.h ├── examples ├── CMakeLists.txt ├── effcee-example-driver.py ├── example_data.txt └── main.cc ├── fuzzer ├── CMakeLists.txt └── effcee_fuzz.cc ├── kokoro ├── check-format │ ├── build-docker.sh │ ├── build.sh │ └── presubmit.cfg ├── linux-clang-debug │ ├── build.sh │ ├── continuous.cfg │ └── presubmit.cfg ├── linux-clang-release-bazel │ ├── build.sh │ ├── continuous.cfg │ └── presubmit.cfg ├── linux-clang-release │ ├── build.sh │ ├── continuous.cfg │ └── presubmit.cfg ├── linux-gcc-debug │ ├── build.sh │ ├── continuous.cfg │ └── presubmit.cfg ├── linux-gcc-release │ ├── build.sh │ ├── continuous.cfg │ └── presubmit.cfg ├── macos-clang-debug │ ├── build.sh │ ├── continuous.cfg │ └── presubmit.cfg ├── macos-clang-release-bazel │ ├── build.sh │ ├── continuous.cfg │ └── presubmit.cfg ├── macos-clang-release │ ├── build.sh │ ├── continuous.cfg │ └── presubmit.cfg ├── scripts │ ├── linux │ │ ├── build-docker.sh │ │ └── build.sh │ ├── macos │ │ └── build.sh │ └── windows │ │ └── build.bat ├── windows-msvc-2019-release │ ├── build.bat │ ├── continuous.cfg │ └── presubmit.cfg ├── windows-msvc-2022-debug │ ├── build.bat │ ├── continuous.cfg │ └── presubmit.cfg └── windows-msvc-2022-release │ ├── build.bat │ ├── continuous.cfg │ └── presubmit.cfg ├── third_party └── CMakeLists.txt └── utils ├── check_code_format.sh └── git-sync-deps /.bazelrc: -------------------------------------------------------------------------------- 1 | # Enable Bzlmod for every Bazel command 2 | common --enable_bzlmod 3 | -------------------------------------------------------------------------------- /.bazelversion: -------------------------------------------------------------------------------- 1 | 7.0.2 2 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: Google 4 | DerivePointerAlignment: false 5 | PointerAlignment: Left 6 | ... 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | build-*/ 3 | out/ 4 | *.pyc 5 | *.swp 6 | *~ 7 | compile_commands.json 8 | .ycm_extra_conf.py 9 | cscope.* 10 | third_party/re2/ 11 | third_party/googletest/ 12 | third_party/abseil-cpp/ 13 | third_party/abseil_cpp/ 14 | bazel-* 15 | MODULE.bazel.lock 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of Effcee authors for copyright purposes. 2 | # This file is distinct from the CONTRIBUTORS files. 3 | # See the latter for an explanation. 4 | 5 | # Names should be added to this file as: 6 | # Name or Organization 7 | # The email address is not required for organizations. 8 | 9 | Google Inc. 10 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | package( 2 | default_visibility = ["//visibility:private"], 3 | ) 4 | 5 | # Description: 6 | # 7 | # Effcee is a C++ library for stateful pattern matching of strings inspired by 8 | # LLVM's FileCheck. 9 | 10 | licenses(["notice"]) # Apache 2.0 11 | 12 | exports_files([ 13 | "CHANGES", 14 | "LICENSE", 15 | ], 16 | visibility = ["//visibility:public"], 17 | ) 18 | 19 | ## This is the main functionality. 20 | cc_library( 21 | name = "effcee", 22 | srcs = glob( 23 | ["effcee/*.cc"], 24 | exclude = ["effcee/*_test.cc"], 25 | ), 26 | hdrs = glob(["effcee/*.h"]), 27 | compatible_with = [ 28 | ], 29 | deps = [ 30 | "@re2//:re2", 31 | ], 32 | visibility = ["//visibility:public"], 33 | ) 34 | 35 | ## An example binary showing usage 36 | cc_binary( 37 | name = "effcee_example", 38 | srcs = ["examples/main.cc"], 39 | deps = [ ":effcee" ], 40 | ) 41 | 42 | # Test effcee_example executable 43 | py_test( 44 | name = "effcee_example_test", 45 | srcs = ["examples/effcee-example-driver.py"], 46 | main = "examples/effcee-example-driver.py", 47 | data = [ ":effcee_example", "examples/example_data.txt" ], 48 | args = [ 49 | "$(location effcee_example)", 50 | "examples/example_data.txt", 51 | "'CHECK: Hello'", 52 | "'CHECK-SAME: world'", 53 | "'CHECK-NEXT: Bees'", 54 | "'CHECK-NOT: Sting'", 55 | "'CHECK: Honey'", 56 | ], 57 | size = "small", 58 | ) 59 | 60 | # Unit tests 61 | 62 | cc_test( 63 | name = "check_test", 64 | srcs = ["effcee/check_test.cc"], 65 | deps = [ 66 | ":effcee", 67 | "@googletest//:gtest_main", 68 | "@googletest//:gtest", 69 | ], 70 | size = "small", 71 | ) 72 | 73 | cc_test( 74 | name = "cursor_test", 75 | srcs = ["effcee/cursor_test.cc"], 76 | deps = [ 77 | ":effcee", 78 | "@googletest//:gtest_main", 79 | "@googletest//:gtest", 80 | ], 81 | size = "small", 82 | ) 83 | 84 | cc_test( 85 | name = "diagnostic_test", 86 | srcs = ["effcee/diagnostic_test.cc"], 87 | deps = [ 88 | ":effcee", 89 | "@googletest//:gtest_main", 90 | "@googletest//:gtest", 91 | ], 92 | size = "small", 93 | ) 94 | 95 | cc_test( 96 | name = "match_test", 97 | srcs = ["effcee/match_test.cc"], 98 | deps = [ 99 | ":effcee", 100 | "@googletest//:gtest_main", 101 | "@googletest//:gtest", 102 | ], 103 | size = "small", 104 | ) 105 | 106 | cc_test( 107 | name = "options_test", 108 | srcs = ["effcee/options_test.cc"], 109 | deps = [ 110 | ":effcee", 111 | "@googletest//:gtest_main", 112 | "@googletest//:gtest", 113 | ], 114 | size = "small", 115 | ) 116 | 117 | cc_test( 118 | name = "result_test", 119 | srcs = ["effcee/result_test.cc"], 120 | deps = [ 121 | ":effcee", 122 | "@googletest//:gtest_main", 123 | "@googletest//:gtest", 124 | ], 125 | size = "small", 126 | ) 127 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | Revision history for Effcee 2 | 3 | v2023.0-dev 2020-06-16 4 | - Add dependency on Abseil 5 | - Set up Kokoro bots 6 | - Remove Travis and Appveyor bot support 7 | - Avoid hardcoding an exact C++11 requirement at project level. 8 | - Avoid subtracting iterators from different string views 9 | 10 | v2019.1 2020-06-16 11 | - Build/CI/release updates 12 | - GitHub repo: switch to 'main' branch, instead of 'master' 13 | - Respect CMAKE_INSTALL_LIBDIR in installed Cmake files 14 | - Travis-CI: On macOS run brew update first 15 | - Fixes: 16 | - protect make_unique with namespace 17 | 18 | v2019.0 2019-09-18 19 | - Add optional tool effcee-fuzz to help run fuzzer cases. 20 | - Build updates 21 | - Add Bazel build rules 22 | - Add Clang warning -Wextra-semi 23 | - Require Python3 24 | - Fix MinGW cross-compile 25 | - Fix tests to work with latest googletest 26 | - Fixes: 27 | - Fail parsing checks when regular expressions are invalid. 28 | - #23: Avoid StringPiece::as_string to enhance portability. 29 | 30 | v2018.1 2018-10-05 31 | - Require CMake 3.1 or later 32 | - Require C++11 33 | - Travis-CI testing uses stock clang, instead of clang-3.6 (which is old by now) 34 | 35 | v2018.0 2018-10-05 36 | - Mature enough for production use by third party projects such as DXC and SPIRV-Tools. 37 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22.1) 2 | project(effcee C CXX) 3 | enable_testing() 4 | 5 | # Require at least C++17 6 | if(NOT CMAKE_CXX_STANDARD) 7 | set(CMAKE_CXX_STANDARD 17) 8 | endif() 9 | if(${CMAKE_CXX_STANDARD} LESS 17) 10 | message(FATAL_ERROR "Effcee requires C++17 or later, but is configured for C++${CMAKE_CXX_STANDARD})") 11 | endif() 12 | 13 | option(EFFCEE_BUILD_TESTING "Enable testing for Effcee" ON) 14 | if(${EFFCEE_BUILD_TESTING}) 15 | message(STATUS "Configuring Effcee to build tests.") 16 | if(MSVC) 17 | # Our tests use ::testing::Combine. Force the ability to use it, working 18 | # around googletest's possibly faulty compiler detection logic. 19 | # See https://github.com/google/googletest/issues/1352 20 | add_definitions(-DGTEST_HAS_COMBINE=1) 21 | endif(MSVC) 22 | else() 23 | message(STATUS "Configuring Effcee to avoid building tests.") 24 | endif() 25 | 26 | option(EFFCEE_BUILD_SAMPLES "Enable building sample Effcee programs" ON) 27 | if(${EFFCEE_BUILD_SAMPLES}) 28 | message(STATUS "Configuring Effcee to build samples.") 29 | else() 30 | message(STATUS "Configuring Effcee to avoid building samples.") 31 | endif() 32 | 33 | # RE2 needs Pthreads on non-WIN32 34 | set(CMAKE_THREAD_LIBS_INIT "") 35 | find_package(Threads) 36 | 37 | include(cmake/setup_build.cmake) 38 | include(cmake/utils.cmake) 39 | include(GNUInstallDirs) 40 | 41 | add_subdirectory(third_party) 42 | add_subdirectory(effcee) 43 | add_subdirectory(fuzzer) 44 | 45 | if(${EFFCEE_BUILD_SAMPLES}) 46 | add_subdirectory(examples) 47 | endif() 48 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Effcee 2 | 3 | Want to contribute? Great! First, read this page (including the small print at 4 | the end). Then, have a look at [`DEVELOPMENT.howto.md`](DEVELOPMENT.howto.md), 5 | which contains useful info to guide you along the way. 6 | 7 | ## Before you contribute 8 | 9 | Before we can use your code, you must sign the 10 | [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual?csw=1) 11 | (CLA), which you can do online. The CLA is necessary mainly because you own the 12 | copyright to your changes, even after your contribution becomes part of our 13 | codebase, so we need your permission to use and distribute your code. We also 14 | need to be sure of various other things -- for instance that you'll tell us if 15 | you know that your code infringes on other people's patents. You don't have to 16 | sign the CLA until after you've submitted your code for review and a member has 17 | approved it, but you must do it before we can put your code into our codebase. 18 | 19 | Before you start working on a larger contribution, you should get in touch with 20 | us first through the issue tracker with your idea so that we can help out and 21 | possibly guide you. Coordinating up front makes it much easier to avoid 22 | frustration later on. 23 | 24 | ## The small print 25 | 26 | Contributions made by corporations are covered by a different agreement than 27 | the one above, the Software Grant and Corporate Contributor License Agreement. 28 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # People who have agreed to one of the CLAs and can contribute patches. 2 | # The AUTHORS file lists the copyright holders; this file 3 | # lists people. For example, Google employees are listed here 4 | # but not in AUTHORS, because Google holds the copyright. 5 | # 6 | # https://developers.google.com/open-source/cla/individual 7 | # https://developers.google.com/open-source/cla/corporate 8 | # 9 | # Names should be added to this file as: 10 | # Name 11 | 12 | Alan Baker 13 | Ehsan Nasiri 14 | David Neto 15 | Lei Zhang 16 | -------------------------------------------------------------------------------- /DEPS: -------------------------------------------------------------------------------- 1 | use_relative_paths = True 2 | 3 | vars = { 4 | 'github': 'https://github.com', 5 | 6 | 'abseil_revision': '8e45685002488b55f24cb67a795eaa8d1c3297a1', 7 | 8 | 'googletest_revision': 'f625681bc4086c35099475f0d17e161f1c6ac9a1', 9 | 10 | 're2_revision': '03da4fc0857c285e3a26782f6bc8931c4c950df4', 11 | } 12 | 13 | deps = { 14 | 'third_party/abseil_cpp': 15 | Var('github') + '/abseil/abseil-cpp.git@' + Var('abseil_revision'), 16 | 17 | 'third_party/googletest': 18 | Var('github') + '/google/googletest.git@' + Var('googletest_revision'), 19 | 20 | 'third_party/re2': 21 | Var('github') + '/google/re2.git@' + Var('re2_revision'), 22 | } 23 | 24 | -------------------------------------------------------------------------------- /DEVELOPMENT.howto.md: -------------------------------------------------------------------------------- 1 | # Developing for Effcee 2 | 3 | Thank you for considering Effcee development! Please make sure you review 4 | [`CONTRIBUTING.md`](CONTRIBUTING.md) for important preliminary info. 5 | 6 | ## Building 7 | 8 | Instructions for first-time building can be found in [`README.md`](README.md). 9 | Incremental build after a source change can be done using `bazel` or `ninja` (or 10 | `cmake --build`) and `ctest` exactly as in the first-time procedure. 11 | 12 | ## Issue tracking 13 | 14 | We use GitHub issues to track bugs, enhancement requests, and questions. See 15 | [the project's Issues page](https://github.com/google/effcee/issues). 16 | 17 | For all but the most trivial changes, we prefer that you file an issue before 18 | submitting a pull request. An issue gives us context for your change: what 19 | problem are you solving, and why. It also allows us to provide feedback on your 20 | proposed solution before you invest a lot of effort implementing it. 21 | 22 | ## Code reviews 23 | 24 | All submissions are subject to review via the GitHub pull review process. 25 | Reviews will cover: 26 | 27 | * *Correctness:* Does it work? Does it work in a multithreaded context? 28 | * *Testing:* New functionality should be accompanied by tests. 29 | * *Testability:* Can it easily be tested? This is proven with accompanying 30 | tests. 31 | * *Design:* Is the solution fragile? Does it fit with the existing code? Would 32 | it easily accommodate anticipated changes? 33 | * *Ease of use:* Can a client get their work done with a minimum of fuss? Are 34 | there unnecessarily surprising details? 35 | * *Consistency:* Does it follow the style guidelines and the rest of the code? 36 | Consistency reduces the work of future readers and maintainers. 37 | * *Portability:* Does it work in many environments? 38 | 39 | To respond to feedback, submit one or more *new* commits to the pull request 40 | branch. The project maintainer will normally clean up the submission by 41 | squashing feedback response commits. We maintain a linear commit history, so 42 | submission will be rebased onto master before merging. 43 | 44 | ## Testing 45 | 46 | There is a lot we won't say about testing. However: 47 | 48 | * Most tests should be small scale, i.e. unit tests. 49 | * Tests should run quickly. 50 | * A test should: 51 | * Check a single behaviour. This often corresponds to a use case. 52 | * Have a three phase structure: setup, action, check. 53 | 54 | ## Coding style 55 | 56 | For C++, we follow the 57 | [Google C++ style guide](https://google.github.io/styleguide/cppguide.html). 58 | 59 | Use `clang-format` to format the code. 60 | 61 | For our Python files, we aim to follow the 62 | [Google Python style guide](https://google.github.io/styleguide/pyguide.html). 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | module(name = "effcee", version = "0.1") 2 | """ 3 | Effcee is a C++ library for stateful pattern matching of strings inspired by 4 | LLVM's FileCheck. 5 | """ 6 | 7 | bazel_dep(name = "rules_python", version = "0.31.0") 8 | python = use_extension("@rules_python//python/extensions:python.bzl", "python") 9 | python.toolchain( 10 | configure_coverage_tool = False, 11 | ignore_root_user_error = True, 12 | python_version = "3.12", 13 | ) 14 | 15 | bazel_dep( 16 | name = "googletest", 17 | version = "1.14.0", 18 | dev_dependency = True, 19 | ) 20 | 21 | bazel_dep(name = "re2", version = "2024-04-01") 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Effcee 2 | 3 | Effcee is a C++ library for stateful pattern matching of strings, inspired by 4 | LLVM's [FileCheck][FileCheck] command. 5 | 6 | Effcee: 7 | 8 | - Is a library, so it can be used for quickly running tests in your own 9 | process. 10 | - Is largely compatible with FileCheck, so tests and test-writing skills are 11 | transferable. 12 | - Has few dependencies: 13 | - The C++11 standard library, and 14 | - [RE2][RE2] for regular expression matching. 15 | - [Abseil][Abseil] utilities for C++ (via RE2). 16 | 17 | ## Example 18 | 19 | The following is from [examples/main.cc](examples/main.cc): 20 | 21 | ```C++ 22 | 23 | #include 24 | #include 25 | 26 | #include "effcee/effcee.h" 27 | 28 | // Checks standard input against the list of checks provided as command line 29 | // arguments. 30 | // 31 | // Example: 32 | // cat <sample_data.txt 33 | // Bees 34 | // Make 35 | // Delicious Honey 36 | // EOF 37 | // effcee-example > input_stream.rdbuf(); 47 | 48 | // Attempt to match. The input and checks arguments can be provided as 49 | // std::string or pointer to char. 50 | auto result = effcee::Match(input_stream.str(), checks_stream.str(), 51 | effcee::Options().SetChecksName("checks")); 52 | 53 | // Successful match result converts to true. 54 | if (result) { 55 | std::cout << "The input matched your check list!" << std::endl; 56 | } else { 57 | // Otherwise, you can get a status code and a detailed message. 58 | switch (result.status()) { 59 | case effcee::Result::Status::NoRules: 60 | std::cout << "error: Expected check rules as command line arguments\n"; 61 | break; 62 | case effcee::Result::Status::Fail: 63 | std::cout << "The input failed to match your check rules:\n"; 64 | break; 65 | default: 66 | break; 67 | } 68 | std::cout << result.message() << std::endl; 69 | return 1; 70 | } 71 | return 0; 72 | } 73 | 74 | ``` 75 | 76 | For more examples, see the matching tests in 77 | [effcee/match_test.cc](effcee/match_test.cc). 78 | 79 | ## Status 80 | 81 | Effcee is mature enough to be relied upon by 82 | [third party projects](#what-uses-effcee), but could be improved. 83 | 84 | What works: 85 | 86 | * All check types: CHECK, CHECK-NEXT, CHECK-SAME, CHECK-DAG, CHECK-LABEL, 87 | CHECK-NOT. 88 | * Check strings can contain: 89 | * fixed strings 90 | * regular expressions 91 | * variable definitions and uses 92 | * Setting a custom check prefix. 93 | * Accurate and helpful reporting of match failures. 94 | 95 | What is left to do: 96 | 97 | * Add an option to define shorthands for regular expressions. 98 | * For example, you could express that if the string `%%` appears where a 99 | regular expression is expected, then it expands to the regular 100 | expression for a local identifier in LLVM assembly language, i.e. 101 | `%[-a-zA-Z$._][-a-zA-Z$._0-9]*`. This enables you to write precise tests 102 | with less fuss. 103 | * Better error reporting for failure to parse the checks list. 104 | * Write a check language reference and tutorial. 105 | 106 | What is left to do, but lower priority: 107 | 108 | * Match full lines. 109 | * Strict whitespace. 110 | * Implicit check-not. 111 | * Variable scoping. 112 | 113 | ## Licensing and contributing 114 | 115 | Effcee is licensed under terms of the [Apache 2.0 license](LICENSE). If you are 116 | interested in contributing to this project, please see 117 | [`CONTRIBUTING.md`](CONTRIBUTING.md). 118 | 119 | This is not an official Google product (experimental or otherwise), it is just 120 | code that happens to be owned by Google. That may change if Effcee gains 121 | contributions from others. See the [`CONTRIBUTING.md`](CONTRIBUTING.md) file for 122 | more information. See also the [`AUTHORS`](AUTHORS) and 123 | [`CONTRIBUTORS`](CONTRIBUTORS) files. 124 | 125 | ## File organization 126 | 127 | - [`effcee`/](effcee) : library source code, and tests 128 | - `third_party/`: third party open source packages, downloaded separately 129 | - [`examples/`](examples): example programs 130 | 131 | Effcee depends on: 132 | * the [RE2][RE2] regular expression library. 133 | * the [Abseil][Abseil] utility library for C++. 134 | 135 | Effcee tests depend on [Googletest][Googletest] and [Python 3][Python]. 136 | 137 | In the following sections, `$SOURCE_DIR` is the directory containing the Effcee 138 | source code. 139 | 140 | ## Getting and building Effcee 141 | 142 | 1) Check out the source code: 143 | 144 | ```sh 145 | git clone https://github.com/google/effcee $SOURCE_DIR 146 | cd $SOURCE_DIR/third_party 147 | git clone https://github.com/google/googletest.git 148 | git clone https://github.com/google/re2.git 149 | git clone https://github.com/abseil/abseil-cpp.git 150 | cd $SOURCE_DIR/ 151 | ``` 152 | 153 | Note: There are two other ways to manage third party sources: 154 | 155 | - If you are building Effcee with Bazel (https://bazel.build), you do not need 156 | to clone the repositories for `googletest` and `re2`. They will be 157 | automatically downloaded by Bazel during build. Bazel will suggest adding 158 | `sha256` attributes to each repository rule to get hermetic builds (these 159 | notices are safe to ignore if you are not interested in hermetic builds). 160 | - If you are building Effcee as part of a larger CMake-based project, add the 161 | RE2 and `googletest` projects before adding Effcee. 162 | - Otherwise, you can set CMake variables to point to third party sources if 163 | they are located somewhere else. See the [Build options](#build-options) 164 | below. 165 | 166 | 2) Ensure you have the requisite tools -- see the tools subsection below. 167 | 168 | 3) Decide where to place the build output. In the following steps, we'll call it 169 | `$BUILD_DIR`. Any new directory should work. We recommend building outside the 170 | source tree, but it is also common to build in a (new) subdirectory of 171 | `$SOURCE_DIR`, such as `$SOURCE_DIR/build`. 172 | 173 | 4a) Build and test with Ninja on Linux or Windows: 174 | 175 | ```sh 176 | cd $BUILD_DIR 177 | cmake -GNinja -DCMAKE_BUILD_TYPE={Debug|Release|RelWithDebInfo} $SOURCE_DIR 178 | ninja 179 | ctest 180 | ``` 181 | 182 | 4b) Or build and test with MSVC on Windows: 183 | 184 | ```sh 185 | cd $BUILD_DIR 186 | cmake $SOURCE_DIR 187 | cmake --build . --config {Release|Debug|MinSizeRel|RelWithDebInfo} 188 | ctest -C {Release|Debug|MinSizeRel|RelWithDebInfo} 189 | ``` 190 | 191 | 4c) Or build with MinGW on Linux for Windows: (Skip building threaded unit tests 192 | due to [Googletest bug 606](https://github.com/google/googletest/issues/606)) 193 | 194 | ```sh 195 | cd $BUILD_DIR 196 | cmake -GNinja -DCMAKE_BUILD_TYPE={Debug|Release|RelWithDebInfo} $SOURCE_DIR \ 197 | -DCMAKE_TOOLCHAIN_FILE=$SOURCE_DIR/cmake/linux-mingw-toolchain.cmake \ 198 | -Dgtest_disable_pthreads=ON 199 | ninja 200 | ``` 201 | 202 | 4d) Or build with Bazel on Linux: 203 | 204 | ```sh 205 | cd $SOURCE_DIR 206 | bazel build -c opt :all 207 | ``` 208 | 209 | After a successful build, you should have a `libeffcee` library under the 210 | `$BUILD_DIR/effcee/` directory (or `$SOURCE_DIR/bazel-bin` when building with 211 | Bazel). 212 | 213 | The default behavior on MSVC is to link with the static CRT. If you would like 214 | to change this behavior `-DEFFCEE_ENABLE_SHARED_CRT` may be passed on the cmake 215 | configure line. 216 | 217 | ### Tests 218 | 219 | By default, Effcee registers two tests with `ctest`: 220 | 221 | * `effcee-test`: All library tests, based on Googletest. 222 | * `effcee-example`: Executes the example executable with sample inputs. 223 | 224 | Running `ctest` without arguments will run the tests for Effcee as well as for 225 | RE2. 226 | 227 | You can disable Effcee's tests by using `-DEFFCEE_BUILD_TESTING=OFF` at 228 | configuration time: 229 | 230 | ```sh 231 | cmake -GNinja -DEFFCEE_BUILD_TESTING=OFF ... 232 | ``` 233 | 234 | The RE2 tests run much longer, so if you're working on Effcee alone, we suggest 235 | limiting ctest to tests with prefix `effcee`: 236 | 237 | ctest -R effcee 238 | 239 | Alternately, you can turn off RE2 tests entirely by using 240 | `-DRE2_BUILD_TESTING=OFF` at configuration time: 241 | 242 | ```sh 243 | cmake -GNinja -DRE2_BUILD_TESTING=OFF ... 244 | ``` 245 | 246 | ### Tools you'll need 247 | 248 | For building, testing, and profiling Effcee, the following tools should be 249 | installed regardless of your OS: 250 | 251 | - A compiler supporting C++11. 252 | - [CMake][CMake]: for generating compilation targets. 253 | - [Python 3][Python]: for a test script. 254 | 255 | On Linux, if cross compiling to Windows: - [MinGW][MinGW]: A GCC-based cross 256 | compiler targeting Windows so that generated executables use the Microsoft C 257 | runtime libraries. 258 | 259 | On Windows, the following tools should be installed and available on your path: 260 | 261 | - Visual Studio 2022 or later. Support for VS 2019 is deprecated. 262 | - Git - including the associated tools, Bash, `diff`. 263 | 264 | ### Build options 265 | 266 | Third party source locations: 267 | 268 | - `EFFCEE_GOOGLETEST_DIR`: Location of `googletest` sources, if not under 269 | `third_party`. 270 | - `EFFCEE_RE2_DIR`: Location of `re2` sources, if not under `third_party`. 271 | - `EFFCEE_THIRD_PARTY_ROOT_DIR`: Alternate location for `googletest` and `re2` 272 | subdirectories. This is used if the sources are not located under the 273 | `third_party` directory, and if the previous two variables are not set. 274 | 275 | Compilation options: 276 | 277 | - `DISABLE_RTTI`. Disable runtime type information. Default is enabled. 278 | - `DISABLE_EXCEPTIONS`. Disable exceptions. Default is enabled. 279 | - `EFFCEE_ENABLE_SHARED_CRT`. See above. 280 | 281 | Controlling samples and tests: 282 | 283 | - `EFFCEE_BUILD_SAMPLES`. Should Effcee examples be built? Defaults to `ON`. 284 | - `EFFCEE_BUILD_TESTING`. Should Effcee tests be built? Defaults to `ON`. 285 | - `RE2_BUILD_TESTING`. Should RE2 tests be built? Defaults to `ON`. 286 | 287 | ## Bug tracking 288 | 289 | We track bugs using GitHub -- click on the "Issues" button on 290 | [the project's GitHub page](https://github.com/google/effcee). 291 | 292 | ## What uses Effcee? 293 | 294 | - [Tests](https://github.com/Microsoft/DirectXShaderCompiler/tree/master/tools/clang/test/CodeGenSPIRV) 295 | for SPIR-V code generation in the [DXC][DXC] HLSL compiler. 296 | - Tests for [SPIRV-Tools][SPIRV-Tools] 297 | 298 | ## References 299 | 300 | [CMake]: https://cmake.org/ 301 | [DXC]: https://github.com/Microsoft/DirectXShaderCompiler 302 | [FileCheck]: http://llvm.org/docs/CommandGuide/FileCheck.html 303 | [Googletest]: https://github.com/google/googletest 304 | [MinGW]: http://www.mingw.org/ 305 | [Python]: https://www.python.org/ 306 | [RE2]: https://github.com/google/re2 307 | [Abseil]: https://github.com/abseil/abseil-cpp 308 | [SPIRV-Tools]: https://github.com/KhronosGroup/SPIRV-Tools 309 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "effcee") 2 | -------------------------------------------------------------------------------- /cmake/linux-mingw-toolchain.cmake: -------------------------------------------------------------------------------- 1 | # Copyright 2017 The Effcee Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | SET(CMAKE_SYSTEM_NAME Windows) 16 | 17 | set(MINGW_COMPILER_PREFIX "i686-w64-mingw32" CACHE STRING 18 | "What compiler prefix to use for mingw") 19 | 20 | set(MINGW_SYSROOT "/usr/${MINGW_COMPILER_PREFIX}" CACHE STRING 21 | "What sysroot to use for mingw") 22 | 23 | # Which compilers to use for C and C++ 24 | find_program(CMAKE_RC_COMPILER NAMES ${MINGW_COMPILER_PREFIX}-windres) 25 | find_program(CMAKE_C_COMPILER NAMES ${MINGW_COMPILER_PREFIX}-gcc) 26 | find_program(CMAKE_CXX_COMPILER NAMES ${MINGW_COMPILER_PREFIX}-g++) 27 | 28 | SET(CMAKE_FIND_ROOT_PATH ${MINGW_SYSROOT}) 29 | 30 | # Adjust the default behaviour of the FIND_XXX() commands: 31 | # Search headers and libraries in the target environment; search 32 | # programs in the host environment. 33 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 34 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 35 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 36 | -------------------------------------------------------------------------------- /cmake/setup_build.cmake: -------------------------------------------------------------------------------- 1 | # Copyright 2017 The Effcee Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | if(NOT COMMAND find_host_package) 16 | macro(find_host_package) 17 | find_package(${ARGN}) 18 | endmacro() 19 | endif() 20 | 21 | find_host_package(Python3) 22 | 23 | option(DISABLE_RTTI "Disable RTTI in builds") 24 | if(DISABLE_RTTI) 25 | if(UNIX) 26 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") 27 | endif(UNIX) 28 | endif(DISABLE_RTTI) 29 | 30 | option(DISABLE_EXCEPTIONS "Disables exceptions in builds") 31 | if(DISABLE_EXCEPTIONS) 32 | if(UNIX) 33 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions") 34 | endif(UNIX) 35 | endif(DISABLE_EXCEPTIONS) 36 | 37 | if(WIN32) 38 | # Ensure that gmock compiles the same as the rest of the code, otherwise 39 | # failures will occur. 40 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 41 | endif(WIN32) 42 | 43 | if(WIN32) 44 | # On Windows, CMake by default compiles with the shared CRT. 45 | # Default it to the static CRT. 46 | option(EFFCEE_ENABLE_SHARED_CRT 47 | "Use the shared CRT with MSVC instead of the static CRT" 48 | ${EFFCEE_ENABLE_SHARED_CRT}) 49 | if (NOT EFFCEE_ENABLE_SHARED_CRT) 50 | # Tell MSVC to Link executables statically. 51 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 52 | endif(NOT EFFCEE_ENABLE_SHARED_CRT) 53 | endif(WIN32) 54 | -------------------------------------------------------------------------------- /cmake/utils.cmake: -------------------------------------------------------------------------------- 1 | # Copyright 2017 The Effcee Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Utility functions 16 | 17 | function(effcee_default_c_compile_options TARGET) 18 | if (NOT "${MSVC}") 19 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 20 | if (ENABLE_CODE_COVERAGE) 21 | # The --coverage option is a synonym for -fprofile-arcs -ftest-coverage 22 | # when compiling. 23 | target_compile_options(${TARGET} PRIVATE -g -O0 --coverage) 24 | # The --coverage option is a synonym for -lgcov when linking for gcc. 25 | # For clang, it links in a different library, libclang_rt.profile, which 26 | # requires clang to be built with compiler-rt. 27 | target_link_libraries(${TARGET} PRIVATE --coverage) 28 | endif() 29 | if (NOT EFFCEE_ENABLE_SHARED_CRT) 30 | if (WIN32) 31 | # For MinGW cross compile, statically link to the libgcc runtime. 32 | # But it still depends on MSVCRT.dll. 33 | set_target_properties(${TARGET} PROPERTIES 34 | LINK_FLAGS "-static -static-libgcc") 35 | endif(WIN32) 36 | endif(NOT EFFCEE_ENABLE_SHARED_CRT) 37 | if (UNIX AND NOT MINGW) 38 | target_link_libraries(${TARGET} PUBLIC -pthread) 39 | endif() 40 | if (${CMAKE_C_COMPILER_ID} MATCHES "Clang") 41 | target_compile_options(${TARGET} PRIVATE -Wextra-semi) 42 | endif() 43 | else() 44 | # disable warning C4800: 'int' : forcing value to bool 'true' or 'false' 45 | # (performance warning) 46 | target_compile_options(${TARGET} PRIVATE /wd4800) 47 | endif() 48 | endfunction(effcee_default_c_compile_options) 49 | 50 | function(effcee_default_compile_options TARGET) 51 | effcee_default_c_compile_options(${TARGET}) 52 | # RE2's public header requires C++11. So publicly require C++11 53 | target_compile_features(${TARGET} PUBLIC cxx_std_11) 54 | if (NOT "${MSVC}") 55 | if (NOT EFFCEE_ENABLE_SHARED_CRT) 56 | if (WIN32) 57 | # For MinGW cross compile, statically link to the C++ runtime. 58 | # But it still depends on MSVCRT.dll. 59 | set_target_properties(${TARGET} PROPERTIES 60 | LINK_FLAGS "-static -static-libgcc -static-libstdc++") 61 | endif(WIN32) 62 | endif(NOT EFFCEE_ENABLE_SHARED_CRT) 63 | endif() 64 | endfunction(effcee_default_compile_options) 65 | -------------------------------------------------------------------------------- /effcee/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(effcee 2 | check.cc 3 | match.cc) 4 | effcee_default_compile_options(effcee) 5 | # We need to expose RE2's StringPiece. 6 | target_include_directories(effcee 7 | PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/.. ${EFFCEE_RE2_DIR}) 8 | target_link_libraries(effcee PUBLIC re2 ${CMAKE_THREADS_LIB_INIT}) 9 | set_target_properties(effcee PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON) 10 | 11 | # TODO(dneto): Avoid installing gtest and gtest_main. ?! 12 | install( 13 | FILES 14 | effcee.h 15 | DESTINATION 16 | include/effcee) 17 | install(TARGETS effcee 18 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 19 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) 20 | 21 | if(EFFCEE_BUILD_TESTING) 22 | add_executable(effcee-test 23 | check_test.cc 24 | cursor_test.cc 25 | diagnostic_test.cc 26 | match_test.cc 27 | options_test.cc 28 | result_test.cc) 29 | effcee_default_compile_options(effcee-test) 30 | target_include_directories(effcee-test PRIVATE 31 | ${gmock_SOURCE_DIR}/include 32 | ${gtest_SOURCE_DIR}/include) 33 | target_link_libraries(effcee-test PRIVATE effcee gmock gtest_main) 34 | add_test(NAME effcee-test COMMAND effcee-test) 35 | endif(EFFCEE_BUILD_TESTING) 36 | -------------------------------------------------------------------------------- /effcee/check.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Effcee Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "check.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "cursor.h" 25 | #include "effcee.h" 26 | #include "make_unique.h" 27 | #include "to_string.h" 28 | 29 | using Status = effcee::Result::Status; 30 | using StringPiece = effcee::StringPiece; 31 | using Type = effcee::Check::Type; 32 | 33 | namespace { 34 | 35 | // Returns a table of suffix to type mappings. 36 | const std::vector>& TypeStringTable() { 37 | static std::vector> type_str_table{ 38 | {"", Type::Simple}, {"-NEXT", Type::Next}, {"-SAME", Type::Same}, 39 | {"-DAG", Type::DAG}, {"-LABEL", Type::Label}, {"-NOT", Type::Not}}; 40 | return type_str_table; 41 | } 42 | 43 | // Returns the Check::Type value matching the suffix part of a check rule 44 | // prefix. Assumes |suffix| is valid. 45 | Type TypeForSuffix(StringPiece suffix) { 46 | const auto& type_str_table = TypeStringTable(); 47 | const auto pair_iter = 48 | std::find_if(type_str_table.begin(), type_str_table.end(), 49 | [suffix](const std::pair& elem) { 50 | return suffix == elem.first; 51 | }); 52 | assert(pair_iter != type_str_table.end()); 53 | return pair_iter->second; 54 | } 55 | } // namespace 56 | 57 | namespace effcee { 58 | 59 | int Check::Part::CountCapturingGroups() { 60 | if (type_ == Type::Regex) return RE2(param_).NumberOfCapturingGroups(); 61 | if (type_ == Type::VarDef) return RE2(expression_).NumberOfCapturingGroups(); 62 | return 0; 63 | } 64 | 65 | Check::Check(Type type, StringPiece param) : type_(type), param_(param) { 66 | parts_.push_back(effcee::make_unique(Part::Type::Fixed, param)); 67 | } 68 | 69 | bool Check::Part::MightMatch(const VarMapping& vars) const { 70 | return type_ != Type::VarUse || 71 | vars.find(ToString(VarUseName())) != vars.end(); 72 | } 73 | 74 | std::string Check::Part::Regex(const VarMapping& vars) const { 75 | switch (type_) { 76 | case Type::Fixed: 77 | return RE2::QuoteMeta(param_); 78 | case Type::Regex: 79 | return ToString(param_); 80 | case Type::VarDef: 81 | return std::string("(") + ToString(expression_) + ")"; 82 | case Type::VarUse: { 83 | auto where = vars.find(ToString(VarUseName())); 84 | if (where != vars.end()) { 85 | // Return the escaped form of the current value of the variable. 86 | return RE2::QuoteMeta((*where).second); 87 | } else { 88 | // The variable is not yet set. Should not get here. 89 | return ""; 90 | } 91 | } 92 | } 93 | return ""; // Unreachable. But we need to satisfy GCC. 94 | } 95 | 96 | bool Check::Matches(StringPiece* input, StringPiece* captured, 97 | VarMapping* vars) const { 98 | if (parts_.empty()) return false; 99 | for (auto& part : parts_) { 100 | if (!part->MightMatch(*vars)) return false; 101 | } 102 | 103 | std::unordered_map var_def_indices; 104 | 105 | // Construct a regex for the check patterns. Anchor to the start 106 | // of the input string, but also match any prefix. Do this so we 107 | // can easily skip over any prefix without having to re-match the 108 | // text. 109 | std::ostringstream consume_regex; 110 | // Match any minimal prefix, then start a constructed grouping for the 111 | // pattern of interest. 112 | consume_regex << ".*?("; 113 | int num_captures = 2; // The outer capture, and the constructed capture. 114 | for (auto& part : parts_) { 115 | consume_regex << part->Regex(*vars); 116 | const auto var_def_name = part->VarDefName(); 117 | if (!var_def_name.empty()) { 118 | var_def_indices[num_captures++] = ToString(var_def_name); 119 | } 120 | num_captures += part->NumCapturingGroups(); 121 | } 122 | consume_regex << ")"; // Finish the constructed grouping. 123 | std::unique_ptr captures(new StringPiece[num_captures]); 124 | const bool matched = RE2(consume_regex.str()) 125 | .Match(*input, 0, input->size(), RE2::ANCHOR_START, 126 | captures.get(), num_captures); 127 | if (matched) { 128 | *captured = captures[1]; 129 | input->remove_prefix(captures[0].size()); 130 | // Update the variable mapping. 131 | for (auto& var_def_index : var_def_indices) { 132 | const int index = var_def_index.first; 133 | (*vars)[var_def_index.second] = ToString(captures[index]); 134 | } 135 | } 136 | 137 | return matched; 138 | } 139 | 140 | namespace { 141 | // Returns a Result and a parts list for the given pattern. This splits out 142 | // regular expressions as delimited by {{ and }}, and also variable uses and 143 | // definitions. This can fail when a regular expression is invalid. 144 | std::pair PartsForPattern(StringPiece pattern) { 145 | Check::Parts parts; 146 | StringPiece fixed, regex, var; 147 | 148 | using Type = Check::Part::Type; 149 | 150 | while (!pattern.empty()) { 151 | const auto regex_start = pattern.find("{{"); 152 | const auto regex_end = pattern.find("}}"); 153 | const auto var_start = pattern.find("[["); 154 | const auto var_end = pattern.find("]]"); 155 | const bool regex_exists = 156 | regex_start < regex_end && regex_end < StringPiece::npos; 157 | const bool var_exists = var_start < var_end && var_end < StringPiece::npos; 158 | 159 | if (regex_exists && (!var_exists || regex_start < var_start)) { 160 | const auto consumed = 161 | RE2::Consume(&pattern, "(.*?){{(.*?)}}", &fixed, ®ex); 162 | if (!consumed) { 163 | assert(consumed && 164 | "Did not make forward progress for regex in check rule"); 165 | } 166 | if (!fixed.empty()) { 167 | parts.emplace_back( 168 | effcee::make_unique(Type::Fixed, fixed)); 169 | } 170 | if (!regex.empty()) { 171 | parts.emplace_back( 172 | effcee::make_unique(Type::Regex, regex)); 173 | if (parts.back()->NumCapturingGroups() < 0) { 174 | return std::make_pair( 175 | Result(Result::Status::BadRule, 176 | std::string("invalid regex: ") + ToString(regex)), 177 | Check::Parts()); 178 | } 179 | } 180 | } else if (var_exists && (!regex_exists || var_start < regex_start)) { 181 | const auto consumed = 182 | RE2::Consume(&pattern, "(.*?)\\[\\[(.*?)\\]\\]", &fixed, &var); 183 | if (!consumed) { 184 | assert(consumed && 185 | "Did not make forward progress for var in check rule"); 186 | } 187 | if (!fixed.empty()) { 188 | parts.emplace_back( 189 | effcee::make_unique(Type::Fixed, fixed)); 190 | } 191 | if (!var.empty()) { 192 | auto colon = var.find(":"); 193 | // A colon at the end is useless anyway, so just make it a variable 194 | // use. 195 | if (colon == StringPiece::npos || colon == var.size() - 1) { 196 | parts.emplace_back( 197 | effcee::make_unique(Type::VarUse, var)); 198 | } else { 199 | StringPiece name = var.substr(0, colon); 200 | StringPiece expression = var.substr(colon + 1, StringPiece::npos); 201 | parts.emplace_back(effcee::make_unique( 202 | Type::VarDef, var, name, expression)); 203 | if (parts.back()->NumCapturingGroups() < 0) { 204 | return std::make_pair( 205 | Result( 206 | Result::Status::BadRule, 207 | std::string("invalid regex in variable definition for ") + 208 | ToString(name) + ": " + ToString(expression)), 209 | Check::Parts()); 210 | } 211 | } 212 | } 213 | } else { 214 | // There is no regex, no var def, no var use. Must be a fixed string. 215 | parts.push_back(effcee::make_unique(Type::Fixed, pattern)); 216 | break; 217 | } 218 | } 219 | 220 | return std::make_pair(Result(Result::Status::Ok), std::move(parts)); 221 | } 222 | 223 | } // namespace 224 | 225 | std::pair ParseChecks(StringPiece str, 226 | const Options& options) { 227 | // Returns a pair whose first member is a result constructed from the 228 | // given status and message, and the second member is an empy pattern. 229 | auto failure = [](Status status, StringPiece message) { 230 | return std::make_pair(Result(status, message), CheckList{}); 231 | }; 232 | 233 | if (options.prefix().size() == 0) 234 | return failure(Status::BadOption, "Rule prefix is empty"); 235 | if (RE2::FullMatch(options.prefix(), "\\s+")) 236 | return failure(Status::BadOption, 237 | "Rule prefix is whitespace. That's silly."); 238 | 239 | CheckList check_list; 240 | 241 | const auto quoted_prefix = RE2::QuoteMeta(options.prefix()); 242 | // Match the following parts: 243 | // .*? - Text that is not the rule prefix 244 | // quoted_prefix - A Simple Check prefix 245 | // (-NEXT|-SAME)? - An optional check type suffix. Two shown here. 246 | // : - Colon 247 | // \s* - Whitespace 248 | // (.*?) - Captured parameter 249 | // \s* - Whitespace 250 | // $ - End of line 251 | 252 | const RE2 regexp(std::string(".*?") + quoted_prefix + 253 | "(-NEXT|-SAME|-DAG|-LABEL|-NOT)?" 254 | ":\\s*(.*?)\\s*$"); 255 | Cursor cursor(str); 256 | while (!cursor.Exhausted()) { 257 | const auto line = cursor.RestOfLine(); 258 | 259 | StringPiece matched_param; 260 | StringPiece suffix; 261 | if (RE2::PartialMatch(line, regexp, &suffix, &matched_param)) { 262 | const Type type = TypeForSuffix(suffix); 263 | auto parts = PartsForPattern(matched_param); 264 | if (!parts.first) return std::make_pair(parts.first, CheckList()); 265 | check_list.push_back(Check(type, matched_param, std::move(parts.second))); 266 | } 267 | cursor.AdvanceLine(); 268 | } 269 | 270 | if (check_list.empty()) { 271 | return failure( 272 | Status::NoRules, 273 | std::string("No check rules specified. Looking for prefix ") + 274 | options.prefix()); 275 | } 276 | 277 | if (check_list[0].type() == Type::Same) { 278 | return failure(Status::BadRule, std::string(options.prefix()) + 279 | "-SAME can't be the first check rule"); 280 | } 281 | 282 | return std::make_pair(Result(Result::Status::Ok), check_list); 283 | } 284 | } // namespace effcee 285 | -------------------------------------------------------------------------------- /effcee/check.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Effcee Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef EFFCEE_CHECK_H 16 | #define EFFCEE_CHECK_H 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "effcee.h" 25 | #include "make_unique.h" 26 | 27 | namespace effcee { 28 | 29 | // A mapping from a name to a string value. 30 | using VarMapping = std::unordered_map; 31 | 32 | // A single check indicating something to be matched. 33 | // 34 | // A _positive_ check is _resolved_ when its parameter is matches a part of the 35 | // in the input text. A _negative_ check is _resolved_ when its parameter does 36 | // _not_ match a section of the input between context-dependent start and end 37 | // points. 38 | class Check { 39 | public: 40 | // The type Determines when the check is satisfied. The Not type denotes 41 | // a negative check. The other types denote positive checks. 42 | enum class Type { 43 | Simple, // Matches a string. 44 | Next, // Matches a string, on the line following previous match. 45 | Same, // Matches a string, on the same line as the previous metch. 46 | DAG, // Matches a string, unordered with respect to other 47 | Label, // Like Simple, but resets local variables. 48 | Not, // Given string is not found before next positive match. 49 | }; 50 | 51 | // A Part is a contiguous segment of the check pattern. A part is 52 | // distinguished by how it matches against input. 53 | class Part { 54 | public: 55 | enum class Type { 56 | Fixed, // A fixed string: characters are matched exactly, in sequence. 57 | Regex, // A regular expression 58 | VarDef, // A variable definition 59 | VarUse, // A variable use 60 | }; 61 | 62 | Part(Type type, StringPiece param) 63 | : type_(type), 64 | param_(param), 65 | name_(), 66 | expression_(), 67 | num_capturing_groups_(CountCapturingGroups()) {} 68 | 69 | // A constructor for a VarDef variant. 70 | Part(Type type, StringPiece param, StringPiece name, StringPiece expr) 71 | : type_(type), 72 | param_(param), 73 | name_(name), 74 | expression_(expr), 75 | num_capturing_groups_(CountCapturingGroups()) {} 76 | 77 | // Returns true if this part might match a target string. The only case where 78 | // this is false is for a VarUse part where the variable is not yet defined. 79 | bool MightMatch(const VarMapping& vars) const; 80 | 81 | // Returns a regular expression to match this part, given a mapping of 82 | // variable names to values. If this part is a fixed string or variable use 83 | // then quoting has been applied. 84 | std::string Regex(const VarMapping& vars) const; 85 | 86 | // Returns number of capturing subgroups in the regex for a Regex or VarDef 87 | // part, and 0 for other parts. 88 | int NumCapturingGroups() const { return num_capturing_groups_; } 89 | 90 | // If this is a VarDef, then returns the name of the variable. Otherwise 91 | // returns an empty string. 92 | StringPiece VarDefName() const { return name_; } 93 | 94 | // If this is a VarUse, then returns the name of the variable. Otherwise 95 | // returns an empty string. 96 | StringPiece VarUseName() const { 97 | return type_ == Type::VarUse ? param_ : ""; 98 | } 99 | 100 | private: 101 | // Computes the number of capturing groups in this part. This is zero 102 | // for Fixed and VarUse parts. 103 | int CountCapturingGroups(); 104 | 105 | // The part type. 106 | Type type_; 107 | // The part parameter. For a Regex, VarDef, and VarUse, this does not 108 | // have the delimiters. 109 | StringPiece param_; 110 | 111 | // For a VarDef, the name of the variable. 112 | StringPiece name_; 113 | // For a VarDef, the regex matching the new value for the variable. 114 | StringPiece expression_; 115 | // The number of capturing subgroups in the regex for a Regex or VarDef 116 | // part, and 0 for other kinds of parts. 117 | int num_capturing_groups_; 118 | }; 119 | 120 | using Parts = std::vector>; 121 | 122 | // MSVC needs a default constructor. However, a default-constructed Check 123 | // instance can't be used for matching. 124 | Check() : type_(Type::Simple) {} 125 | 126 | // Construct a Check object of the given type and fixed parameter string. 127 | // In particular, this retains a StringPiece reference to the |param| 128 | // contents, so that string storage should remain valid for the duration 129 | // of this object. 130 | Check(Type type, StringPiece param); 131 | 132 | // Construct a Check object of the given type, with given parameter string 133 | // and specified parts. 134 | Check(Type type, StringPiece param, Parts&& parts) 135 | : type_(type), param_(param), parts_(std::move(parts)) {} 136 | 137 | // Move constructor. 138 | Check(Check&& other) : type_(other.type_), param_(other.param_) { 139 | parts_.swap(other.parts_); 140 | } 141 | // Copy constructor. 142 | Check(const Check& other) : type_(other.type_), param_(other.param_) { 143 | for (const auto& part : other.parts_) { 144 | parts_.push_back(effcee::make_unique(*part)); 145 | } 146 | } 147 | // Copy and move assignment. 148 | Check& operator=(Check other) { 149 | type_ = other.type_; 150 | param_ = other.param_; 151 | std::swap(parts_, other.parts_); 152 | return *this; 153 | } 154 | 155 | // Accessors. 156 | Type type() const { return type_; } 157 | StringPiece param() const { return param_; } 158 | const Parts& parts() const { return parts_; } 159 | 160 | // Tries to match the given string, using |vars| as the variable mapping 161 | // context. A variable use, e.g. '[[X]]', matches the current value for 162 | // that variable in vars, 'X' in this case. A variable definition, 163 | // e.g. '[[XYZ:[0-9]+]]', will match against the regex provdided after the 164 | // colon. If successful, returns true, advances |str| past the matched 165 | // portion, saves the captured substring in |captured|, and sets the value 166 | // of named variables in |vars| with the strings they matched. Otherwise 167 | // returns false and does not update |str| or |captured|. Assumes this 168 | // instance is not default-constructed. 169 | bool Matches(StringPiece* str, StringPiece* captured, VarMapping* vars) const; 170 | 171 | private: 172 | // The type of check. 173 | Type type_; 174 | 175 | // The parameter as given in user input, if any. 176 | StringPiece param_; 177 | 178 | // The parameter, broken down into parts. 179 | Parts parts_; 180 | }; 181 | 182 | // Equality operator for Check. 183 | inline bool operator==(const Check& lhs, const Check& rhs) { 184 | return lhs.type() == rhs.type() && lhs.param() == rhs.param(); 185 | } 186 | 187 | // Inequality operator for Check. 188 | inline bool operator!=(const Check& lhs, const Check& rhs) { 189 | return !(lhs == rhs); 190 | } 191 | 192 | using CheckList = std::vector; 193 | 194 | // Parses |checks_string|, returning a Result status object and the sequence 195 | // of recognized checks, taking |options| into account. The result status 196 | // object indicates success, or failure with a message. 197 | // TODO(dneto): Only matches simple checks for now. 198 | std::pair ParseChecks(StringPiece checks_string, 199 | const Options& options); 200 | 201 | } // namespace effcee 202 | 203 | #endif 204 | -------------------------------------------------------------------------------- /effcee/check_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Effcee Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include "gmock/gmock.h" 17 | 18 | #include "check.h" 19 | 20 | namespace { 21 | 22 | using effcee::Check; 23 | using effcee::Options; 24 | using effcee::CheckList; 25 | using effcee::ParseChecks; 26 | using effcee::Result; 27 | using effcee::StringPiece; 28 | using ::testing::Combine; 29 | using ::testing::Eq; 30 | using ::testing::HasSubstr; 31 | using ::testing::ValuesIn; 32 | 33 | using Part = effcee::Check::Part; 34 | using Status = effcee::Result::Status; 35 | using Type = Check::Type; 36 | using VarMapping = effcee::VarMapping; 37 | 38 | // Check class 39 | 40 | // Returns a vector of all Check types. 41 | std::vector AllTypes() { 42 | return {Type::Simple, Type::Next, Type::Same, 43 | Type::DAG, Type::Label, Type::Not}; 44 | } 45 | 46 | using CheckTypeTest = ::testing::TestWithParam; 47 | 48 | TEST_P(CheckTypeTest, ConstructWithAnyType) { 49 | Check check(GetParam(), ""); 50 | EXPECT_THAT(check.type(), Eq(GetParam())); 51 | } 52 | 53 | INSTANTIATE_TEST_SUITE_P(AllTypes, CheckTypeTest, ValuesIn(AllTypes())); 54 | 55 | using CheckParamTest = ::testing::TestWithParam; 56 | 57 | TEST_P(CheckParamTest, ConstructWithSampleParamValue) { 58 | Check check(Type::Simple, GetParam()); 59 | // The contents are the same. 60 | EXPECT_THAT(check.param(), Eq(GetParam())); 61 | // The referenced storage is the same. 62 | EXPECT_THAT(check.param().data(), Eq(GetParam().data())); 63 | } 64 | 65 | INSTANTIATE_TEST_SUITE_P(SampleParams, CheckParamTest, 66 | ValuesIn(std::vector{ 67 | "", "a b c", "The wind {{in}} the willows\n", 68 | "Bring me back to the mountains of yore."})); 69 | 70 | // Equality operator 71 | TEST(CheckEqualityTest, TrueWhenAllComponentsSame) { 72 | EXPECT_TRUE(Check(Type::Simple, "abc") == Check(Type::Simple, "abc")); 73 | } 74 | 75 | TEST(CheckEqualityTest, FalseWhenTypeDifferent) { 76 | EXPECT_FALSE(Check(Type::Simple, "abc") == Check(Type::Next, "abc")); 77 | } 78 | 79 | TEST(CheckEqualityTest, FalseWhenParamDifferent) { 80 | EXPECT_FALSE(Check(Type::Simple, "abc") == Check(Type::Simple, "def")); 81 | } 82 | 83 | // Inequality operator 84 | TEST(CheckInequalityTest, FalseWhenAllComponentsSame) { 85 | EXPECT_FALSE(Check(Type::Simple, "abc") != Check(Type::Simple, "abc")); 86 | } 87 | 88 | TEST(CheckInequalityTest, TrueWhenTypeDifferent) { 89 | EXPECT_TRUE(Check(Type::Simple, "abc") != Check(Type::Next, "abc")); 90 | } 91 | 92 | TEST(CheckInequalityTest, TrueWhenParamDifferent) { 93 | EXPECT_TRUE(Check(Type::Simple, "abc") != Check(Type::Simple, "def")); 94 | } 95 | 96 | // ParseChecks free function 97 | 98 | TEST(ParseChecks, FreeFunctionLinks) { 99 | std::pair parsed(ParseChecks("", Options())); 100 | } 101 | 102 | TEST(ParseChecks, FailWhenRulePrefixIsEmpty) { 103 | const auto parsed(ParseChecks("CHECK: now", Options().SetPrefix(""))); 104 | const Result& result = parsed.first; 105 | const CheckList& pattern = parsed.second; 106 | EXPECT_THAT(result.status(), Eq(Status::BadOption)); 107 | EXPECT_THAT(result.message(), Eq("Rule prefix is empty")); 108 | EXPECT_THAT(pattern.size(), Eq(0)); 109 | } 110 | 111 | TEST(ParseChecks, FailWhenRulePrefixIsWhitespace) { 112 | const auto parsed(ParseChecks("CHECK: now", Options().SetPrefix("\t\n "))); 113 | const Result& result = parsed.first; 114 | const CheckList& pattern = parsed.second; 115 | EXPECT_THAT(result.status(), Eq(Status::BadOption)); 116 | EXPECT_THAT(result.message(), 117 | Eq("Rule prefix is whitespace. That's silly.")); 118 | EXPECT_THAT(pattern.size(), Eq(0)); 119 | } 120 | 121 | TEST(ParseChecks, FailWhenChecksAbsent) { 122 | const auto parsed(ParseChecks("no checks", Options())); 123 | const Result& result = parsed.first; 124 | const CheckList& pattern = parsed.second; 125 | EXPECT_THAT(result.status(), Eq(Status::NoRules)); 126 | EXPECT_THAT(result.message(), 127 | Eq("No check rules specified. Looking for prefix CHECK")); 128 | EXPECT_THAT(pattern.size(), Eq(0)); 129 | } 130 | 131 | TEST(ParseChecks, FailWhenChecksAbsentWithCustomPrefix) { 132 | const auto parsed(ParseChecks("CHECK: now", Options().SetPrefix("FOO"))); 133 | const Result& result = parsed.first; 134 | const CheckList& pattern = parsed.second; 135 | EXPECT_THAT(result.status(), Eq(Status::NoRules)); 136 | EXPECT_THAT(result.message(), 137 | Eq("No check rules specified. Looking for prefix FOO")); 138 | EXPECT_THAT(pattern.size(), Eq(0)); 139 | } 140 | 141 | TEST(ParseChecks, FindSimpleCheck) { 142 | const auto parsed = ParseChecks("CHECK: now", Options()); 143 | EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); 144 | EXPECT_THAT(parsed.second, Eq(CheckList({Check(Type::Simple, "now")}))); 145 | } 146 | 147 | TEST(ParseChecks, FindSimpleCheckWithCustomPrefix) { 148 | const auto parsed = ParseChecks("FOO: how", Options().SetPrefix("FOO")); 149 | EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); 150 | EXPECT_THAT(parsed.second, Eq(CheckList({Check(Type::Simple, "how")}))); 151 | } 152 | 153 | TEST(ParseChecks, FindSimpleCheckWithCustomPrefixHavingRegexpMetachars) { 154 | const auto parsed = ParseChecks("[::alpha::]^\\d: how", 155 | Options().SetPrefix("[::alpha::]^\\d")); 156 | EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); 157 | EXPECT_THAT(parsed.second, Eq(CheckList({Check(Type::Simple, "how")}))); 158 | } 159 | 160 | TEST(ParseChecks, FindSimpleCheckPartwayThroughLine) { 161 | const auto parsed = ParseChecks("some other garbageCHECK: now", Options()); 162 | EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); 163 | EXPECT_THAT(parsed.second, Eq(CheckList({Check(Type::Simple, "now")}))); 164 | } 165 | 166 | TEST(ParseChecks, FindSimpleCheckCheckListWithoutSurroundingWhitespace) { 167 | const auto parsed = ParseChecks("CHECK:now", Options()); 168 | EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); 169 | EXPECT_THAT(parsed.second, Eq(CheckList({Check(Type::Simple, "now")}))); 170 | } 171 | 172 | TEST(ParseChecks, FindSimpleCheckCheckListWhileStrippingSurroundingWhitespace) { 173 | const auto parsed = ParseChecks("CHECK: \t now\t\t ", Options()); 174 | EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); 175 | EXPECT_THAT(parsed.second, Eq(CheckList({Check(Type::Simple, "now")}))); 176 | } 177 | 178 | TEST(ParseChecks, FindSimpleCheckCountsLinesCorrectly) { 179 | const auto parsed = ParseChecks("\n\nCHECK: now", Options()); 180 | EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); 181 | EXPECT_THAT(parsed.second, Eq(CheckList({Check(Type::Simple, "now")}))); 182 | } 183 | 184 | TEST(ParseChecks, FindSimpleChecksOnSeparateLines) { 185 | const auto parsed = 186 | ParseChecks("CHECK: now\n\n\nCHECK: and \n CHECK: then", Options()); 187 | EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); 188 | EXPECT_THAT(parsed.second, Eq(CheckList({Check(Type::Simple, "now"), 189 | Check(Type::Simple, "and"), 190 | Check(Type::Simple, "then")}))); 191 | } 192 | 193 | TEST(ParseChecks, FindSimpleChecksOnlyOncePerLine) { 194 | const auto parsed = ParseChecks("CHECK: now CHECK: then", Options()); 195 | EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); 196 | EXPECT_THAT(parsed.second, 197 | Eq(CheckList({Check(Type::Simple, "now CHECK: then")}))); 198 | } 199 | 200 | // Test parsing of the different check rule types. 201 | 202 | using ParseChecksTypeTest = ::testing::TestWithParam< 203 | std::tuple>>; 204 | 205 | TEST_P(ParseChecksTypeTest, Successful) { 206 | const auto& prefix = std::get<0>(GetParam()); 207 | const auto& type_str = std::get<0>(std::get<1>(GetParam())); 208 | const Type& type = std::get<1>(std::get<1>(GetParam())); 209 | // A CHECK-SAME rule can't appear first, so insert a CHECK: rule first. 210 | const std::string input = prefix + ": here\n" + prefix + type_str + ": now"; 211 | const auto parsed = ParseChecks(input, Options().SetPrefix(prefix)); 212 | EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); 213 | EXPECT_THAT(parsed.second, 214 | Eq(CheckList({Check(Type::Simple, "here"), Check(type, "now")}))); 215 | } 216 | 217 | // Returns a vector of pairs. Each pair has first member being a check type 218 | // suffix, and the second member is the corresponding check type. 219 | std::vector> AllCheckTypesAsPairs() { 220 | return { 221 | {"", Type::Simple}, {"-NEXT", Type::Next}, {"-SAME", Type::Same}, 222 | {"-DAG", Type::DAG}, {"-LABEL", Type::Label}, {"-NOT", Type::Not}, 223 | }; 224 | } 225 | 226 | INSTANTIATE_TEST_SUITE_P(AllCheckTypes, ParseChecksTypeTest, 227 | Combine(ValuesIn(std::vector{"CHECK", 228 | "FOO"}), 229 | ValuesIn(AllCheckTypesAsPairs()))); 230 | 231 | using ParseChecksTypeFailTest = ::testing::TestWithParam< 232 | std::tuple>>; 233 | 234 | // This is just one way to fail. 235 | TEST_P(ParseChecksTypeFailTest, FailureWhenNoColon) { 236 | const auto& prefix = std::get<0>(GetParam()); 237 | const auto& type_str = std::get<0>(std::get<1>(GetParam())); 238 | const std::string input = prefix + type_str + "BAD now"; 239 | const auto parsed = ParseChecks(input, Options().SetPrefix(prefix)); 240 | EXPECT_THAT(parsed.first.status(), Eq(Status::NoRules)); 241 | EXPECT_THAT(parsed.second, Eq(CheckList{})); 242 | } 243 | 244 | INSTANTIATE_TEST_SUITE_P(AllCheckTypes, ParseChecksTypeFailTest, 245 | Combine(ValuesIn(std::vector{"CHECK", 246 | "FOO"}), 247 | ValuesIn(AllCheckTypesAsPairs()))); 248 | 249 | TEST(ParseChecks, BadRegexpMatchTrailingSlashFails) { 250 | const auto parsed = ParseChecks("CHECK: {{\\}}", Options()); 251 | EXPECT_THAT(parsed.first.status(), Eq(Status::BadRule)); 252 | EXPECT_THAT(parsed.first.message(), HasSubstr("invalid regex: \\")); 253 | EXPECT_THAT(parsed.second, Eq(CheckList({}))); 254 | } 255 | 256 | TEST(ParseChecks, BadRegexpVardefUnboundOptionalFails) { 257 | const auto parsed = ParseChecks("CHECK: [[VAR:?]]", Options()); 258 | EXPECT_THAT(parsed.first.status(), Eq(Status::BadRule)); 259 | EXPECT_THAT(parsed.first.message(), 260 | HasSubstr("invalid regex in variable definition for VAR: ?")); 261 | EXPECT_THAT(parsed.second, Eq(CheckList({}))); 262 | } 263 | 264 | TEST(ParseChecks, CheckSameCantBeFirst) { 265 | const auto parsed = ParseChecks("CHECK-SAME: now", Options()); 266 | EXPECT_THAT(parsed.first.status(), Eq(Status::BadRule)); 267 | EXPECT_THAT(parsed.first.message(), 268 | HasSubstr("CHECK-SAME can't be the first check rule")); 269 | EXPECT_THAT(parsed.second, Eq(CheckList({}))); 270 | } 271 | 272 | TEST(ParseChecks, CheckSameCantBeFirstDifferentPrefix) { 273 | const auto parsed = ParseChecks("BOO-SAME: now", Options().SetPrefix("BOO")); 274 | EXPECT_THAT(parsed.first.status(), Eq(Status::BadRule)); 275 | EXPECT_THAT(parsed.first.message(), 276 | HasSubstr("BOO-SAME can't be the first check rule")); 277 | EXPECT_THAT(parsed.second, Eq(CheckList({}))); 278 | } 279 | 280 | // Check::Matches 281 | struct CheckMatchCase { 282 | std::string input; 283 | Check check; 284 | bool expected; 285 | std::string remaining; 286 | std::string captured; 287 | }; 288 | 289 | using CheckMatchTest = ::testing::TestWithParam; 290 | 291 | TEST_P(CheckMatchTest, Samples) { 292 | StringPiece str = GetParam().input; 293 | StringPiece captured; 294 | VarMapping vars; 295 | const bool matched = GetParam().check.Matches(&str, &captured, &vars); 296 | EXPECT_THAT(matched, Eq(GetParam().expected)) 297 | << "Failed on input " << GetParam().input; 298 | EXPECT_THAT(std::string(str.data(), str.size()), Eq(GetParam().remaining)); 299 | EXPECT_THAT(std::string(captured.data(), captured.size()), 300 | Eq(GetParam().captured)); 301 | EXPECT_TRUE(vars.empty()); 302 | } 303 | 304 | INSTANTIATE_TEST_SUITE_P( 305 | Simple, CheckMatchTest, 306 | ValuesIn(std::vector{ 307 | {"hello", Check(Type::Simple, "hello"), true, "", "hello"}, 308 | {"world", Check(Type::Simple, "hello"), false, "world", ""}, 309 | {"in hello now", Check(Type::Simple, "hello"), true, " now", "hello"}, 310 | {"hello", Check(Type::Same, "hello"), true, "", "hello"}, 311 | {"world", Check(Type::Same, "hello"), false, "world", ""}, 312 | {"in hello now", Check(Type::Same, "hello"), true, " now", "hello"}, 313 | {"hello", Check(Type::Next, "hello"), true, "", "hello"}, 314 | {"world", Check(Type::Next, "hello"), false, "world", ""}, 315 | {"in hello now", Check(Type::Next, "hello"), true, " now", "hello"}, 316 | {"hello", Check(Type::DAG, "hello"), true, "", "hello"}, 317 | {"world", Check(Type::DAG, "hello"), false, "world", ""}, 318 | {"in hello now", Check(Type::DAG, "hello"), true, " now", "hello"}, 319 | {"hello", Check(Type::Label, "hello"), true, "", "hello"}, 320 | {"world", Check(Type::Label, "hello"), false, "world", ""}, 321 | {"in hello now", Check(Type::Label, "hello"), true, " now", "hello"}, 322 | {"hello", Check(Type::Label, "hello"), true, "", "hello"}, 323 | {"world", Check(Type::Label, "hello"), false, "world", ""}, 324 | {"in hello now", Check(Type::Label, "hello"), true, " now", "hello"}, 325 | {"hello", Check(Type::Not, "hello"), true, "", "hello"}, 326 | {"world", Check(Type::Not, "hello"), false, "world", ""}, 327 | {"in hello now", Check(Type::Not, "hello"), true, " now", "hello"}, 328 | })); 329 | 330 | // Check::Part::Regex 331 | 332 | TEST(CheckPart, FixedPartRegex) { 333 | VarMapping vm; 334 | EXPECT_THAT(Part(Part::Type::Fixed, "abc").Regex(vm), Eq("abc")); 335 | EXPECT_THAT(Part(Part::Type::Fixed, "a.bc").Regex(vm), Eq("a\\.bc")); 336 | EXPECT_THAT(Part(Part::Type::Fixed, "a?bc").Regex(vm), Eq("a\\?bc")); 337 | EXPECT_THAT(Part(Part::Type::Fixed, "a+bc").Regex(vm), Eq("a\\+bc")); 338 | EXPECT_THAT(Part(Part::Type::Fixed, "a*bc").Regex(vm), Eq("a\\*bc")); 339 | EXPECT_THAT(Part(Part::Type::Fixed, "a[b]").Regex(vm), Eq("a\\[b\\]")); 340 | EXPECT_THAT(Part(Part::Type::Fixed, "a[-]").Regex(vm), Eq("a\\[\\-\\]")); 341 | EXPECT_THAT(Part(Part::Type::Fixed, "a(-)b").Regex(vm), Eq("a\\(\\-\\)b")); 342 | } 343 | 344 | TEST(CheckPart, RegexPartRegex) { 345 | VarMapping vm; 346 | EXPECT_THAT(Part(Part::Type::Regex, "abc").Regex(vm), Eq("abc")); 347 | EXPECT_THAT(Part(Part::Type::Regex, "a.bc").Regex(vm), Eq("a.bc")); 348 | EXPECT_THAT(Part(Part::Type::Regex, "a?bc").Regex(vm), Eq("a?bc")); 349 | EXPECT_THAT(Part(Part::Type::Regex, "a+bc").Regex(vm), Eq("a+bc")); 350 | EXPECT_THAT(Part(Part::Type::Regex, "a*bc").Regex(vm), Eq("a*bc")); 351 | EXPECT_THAT(Part(Part::Type::Regex, "a[b]").Regex(vm), Eq("a[b]")); 352 | EXPECT_THAT(Part(Part::Type::Regex, "a[-]").Regex(vm), Eq("a[-]")); 353 | EXPECT_THAT(Part(Part::Type::Regex, "a(-)b").Regex(vm), Eq("a(-)b")); 354 | } 355 | 356 | } // namespace 357 | 358 | -------------------------------------------------------------------------------- /effcee/cursor.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Effcee Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef EFFCEE_CURSOR_H 16 | #define EFFCEE_CURSOR_H 17 | 18 | #include 19 | #include 20 | 21 | #include "re2/stringpiece.h" 22 | 23 | namespace effcee { 24 | 25 | using StringPiece = re2::StringPiece; 26 | 27 | // Represents a position in a StringPiece, while tracking line number. 28 | class Cursor { 29 | 30 | public: 31 | explicit Cursor(StringPiece str) 32 | : remaining_(str), line_num_(1) {} 33 | 34 | StringPiece remaining() const { return remaining_; } 35 | // Returns the current 1-based line number. 36 | int line_num() const { return line_num_; } 37 | 38 | // Returns true if the remaining text is empty. 39 | bool Exhausted() const { return remaining_.empty(); } 40 | 41 | // Returns a string piece from the current position until the end of the line 42 | // or the end of input, up to and including the newline. 43 | StringPiece RestOfLine() const { 44 | const auto newline_pos = remaining_.find('\n'); 45 | return remaining_.substr(0, newline_pos + (newline_pos != StringPiece::npos)); 46 | } 47 | 48 | // Advance |n| characters. Does not adjust line count. The next |n| 49 | // characters should not contain newlines if line numbering is to remain 50 | // up to date. Returns this object. 51 | Cursor& Advance(size_t n) { remaining_.remove_prefix(n); return *this; } 52 | 53 | // Advances the cursor by a line. If no text remains, then does nothing. 54 | // Otherwise removes the first line (including newline) and increments the 55 | // line count. If there is no newline then the remaining string becomes 56 | // empty. Returns this object. 57 | Cursor& AdvanceLine() { 58 | if (remaining_.size()) { 59 | Advance(RestOfLine().size()); 60 | ++line_num_; 61 | } 62 | return *this; 63 | } 64 | 65 | private: 66 | // The remaining text, after all previous advancements. References the 67 | // original string storage. 68 | StringPiece remaining_; 69 | // The current 1-based line number. 70 | int line_num_; 71 | }; 72 | 73 | // Returns string containing a description of the line containing a given 74 | // subtext, with a message, and a caret displaying the subtext position. 75 | // Assumes subtext does not contain a newline. 76 | inline std::string LineMessage(StringPiece text, StringPiece subtext, 77 | StringPiece message) { 78 | Cursor c(text); 79 | StringPiece full_line = c.RestOfLine(); 80 | const auto* subtext_end = subtext.data() + subtext.size(); 81 | while (subtext_end - (full_line.data() + full_line.size()) > 0) { 82 | c.AdvanceLine(); 83 | full_line = c.RestOfLine(); 84 | } 85 | const char* full_line_newline = 86 | full_line.find('\n') == StringPiece::npos ? "\n" : ""; 87 | const auto column = size_t(subtext.data() - full_line.data()); 88 | 89 | std::ostringstream out; 90 | out << ":" << c.line_num() << ":" << (1 + column) << ": " << message << "\n" 91 | << full_line << full_line_newline << std::string(column, ' ') << "^\n"; 92 | 93 | return out.str(); 94 | } 95 | 96 | } // namespace effcee 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /effcee/cursor_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Effcee Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "gmock/gmock.h" 16 | 17 | #include "cursor.h" 18 | 19 | namespace { 20 | 21 | using effcee::Cursor; 22 | using effcee::LineMessage; 23 | using effcee::StringPiece; 24 | using ::testing::Eq; 25 | using ::testing::HasSubstr; 26 | 27 | // text method 28 | 29 | // remaining and Advance methods 30 | TEST(Cursor, AdvanceReturnsTheCursorItself) { 31 | Cursor c("foo"); 32 | EXPECT_THAT(&c.Advance(1), Eq(&c)); 33 | } 34 | 35 | TEST(Cursor, RemainingBeginsEqualToText) { 36 | const char* original = "The Smiths"; 37 | Cursor c(original); 38 | EXPECT_THAT(c.remaining().data(), Eq(original)); 39 | } 40 | 41 | TEST(Cursor, RemainingDiminishesByPreviousAdvanceCalls) { 42 | const char* original = "The Smiths are a great 80s band"; 43 | Cursor c(original); 44 | c.Advance(4); 45 | EXPECT_THAT(c.remaining(), Eq("Smiths are a great 80s band")); 46 | EXPECT_THAT(c.remaining().data(), Eq(original + 4)); 47 | c.Advance(11); 48 | EXPECT_THAT(c.remaining(), Eq("a great 80s band")); 49 | EXPECT_THAT(c.remaining().data(), Eq(original + 15)); 50 | c.Advance(c.remaining().size()); 51 | EXPECT_THAT(c.remaining(), Eq("")); 52 | EXPECT_THAT(c.remaining().data(), Eq(original + 31)); 53 | } 54 | 55 | // Exhausted method 56 | 57 | TEST(Cursor, ExhaustedImmediatelyWhenStartingWithEmptyString) { 58 | Cursor c(""); 59 | EXPECT_TRUE(c.Exhausted()); 60 | } 61 | 62 | TEST(Cursor, ExhaustedWhenRemainingIsEmpty) { 63 | Cursor c("boo"); 64 | EXPECT_FALSE(c.Exhausted()); 65 | c.Advance(2); 66 | EXPECT_FALSE(c.Exhausted()); 67 | c.Advance(1); 68 | EXPECT_TRUE(c.Exhausted()); 69 | } 70 | 71 | // RestOfLine method 72 | 73 | TEST(Cursor, RestOfLineOnEmptyReturnsEmpty) { 74 | const char* original = ""; 75 | Cursor c(original); 76 | EXPECT_THAT(c.RestOfLine(), Eq("")); 77 | EXPECT_THAT(c.RestOfLine().data(), Eq(original)); 78 | } 79 | 80 | TEST(Cursor, RestOfLineWithoutNewline) { 81 | Cursor c("The end"); 82 | EXPECT_THAT(c.RestOfLine(), Eq("The end")); 83 | } 84 | 85 | TEST(Cursor, RestOfLineGetsLineUpToAndIncludingNewline) { 86 | Cursor c("The end\nOf an era"); 87 | EXPECT_THAT(c.RestOfLine(), Eq("The end\n")); 88 | } 89 | 90 | TEST(Cursor, RestOfLineGetsOnlyFromRemainingText) { 91 | Cursor c("The end\nOf an era"); 92 | c.Advance(4); 93 | EXPECT_THAT(c.remaining(), Eq("end\nOf an era")); 94 | EXPECT_THAT(c.RestOfLine(), Eq("end\n")); 95 | } 96 | 97 | // AdvanceLine and line_num methods 98 | 99 | TEST(Cursor, AdvanceLineReturnsTheCursorItself) { 100 | Cursor c("foo\nbar"); 101 | EXPECT_THAT(&c.AdvanceLine(), Eq(&c)); 102 | } 103 | 104 | TEST(Cursor, AdvanceLineWalksThroughTextByLineAndCountsLines) { 105 | const char* original = "The end\nOf an era\nIs here"; 106 | Cursor c(original); 107 | EXPECT_THAT(c.line_num(), Eq(1)); 108 | c.AdvanceLine(); 109 | EXPECT_THAT(c.line_num(), Eq(2)); 110 | EXPECT_THAT(c.remaining(), Eq("Of an era\nIs here")); 111 | EXPECT_THAT(c.remaining().data(), Eq(original + 8)); 112 | c.AdvanceLine(); 113 | EXPECT_THAT(c.line_num(), Eq(3)); 114 | EXPECT_THAT(c.remaining(), Eq("Is here")); 115 | EXPECT_THAT(c.remaining().data(), Eq(original + 18)); 116 | c.AdvanceLine(); 117 | EXPECT_THAT(c.line_num(), Eq(4)); 118 | EXPECT_THAT(c.remaining(), Eq("")); 119 | EXPECT_THAT(c.remaining().data(), Eq(original + 25)); 120 | } 121 | 122 | TEST(Cursor, AdvanceLineIsNoopAfterEndIsReached) { 123 | Cursor c("One\nTwo"); 124 | c.AdvanceLine(); 125 | EXPECT_THAT(c.line_num(), Eq(2)); 126 | EXPECT_THAT(c.remaining(), Eq("Two")); 127 | c.AdvanceLine(); 128 | EXPECT_THAT(c.line_num(), Eq(3)); 129 | EXPECT_THAT(c.remaining(), Eq("")); 130 | c.AdvanceLine(); 131 | EXPECT_THAT(c.line_num(), Eq(3)); 132 | EXPECT_THAT(c.remaining(), Eq("")); 133 | } 134 | 135 | // LineMessage free function. 136 | 137 | TEST(LineMessage, SubtextIsFirst) { 138 | StringPiece text("Foo\nBar"); 139 | StringPiece subtext(text.data(), 3); 140 | EXPECT_THAT(LineMessage(text, subtext, "loves quiche"), 141 | Eq(":1:1: loves quiche\nFoo\n^\n")); 142 | } 143 | 144 | TEST(LineMessage, SubtextDoesNotEndInNewline) { 145 | StringPiece text("Foo\nBar"); 146 | StringPiece subtext(text.data() + 4, 3); 147 | EXPECT_THAT(LineMessage(text, subtext, "loves quiche"), 148 | Eq(":2:1: loves quiche\nBar\n^\n")); 149 | } 150 | 151 | TEST(LineMessage, SubtextPartwayThroughItsLine) { 152 | StringPiece text("Food Life\nBar"); 153 | StringPiece subtext(text.data() + 5, 3); // "Lif" 154 | EXPECT_THAT(LineMessage(text, subtext, "loves quiche"), 155 | Eq(":1:6: loves quiche\nFood Life\n ^\n")); 156 | } 157 | 158 | TEST(LineMessage, SubtextOnSubsequentLine) { 159 | StringPiece text("Food Life\nBar Fight\n"); 160 | StringPiece subtext(text.data() + 14, 5); // "Fight" 161 | EXPECT_THAT(LineMessage(text, subtext, "loves quiche"), 162 | Eq(":2:5: loves quiche\nBar Fight\n ^\n")); 163 | } 164 | 165 | TEST(LineMessage, SubtextIsEmptyAndInMiddle) { 166 | StringPiece text("Food"); 167 | StringPiece subtext(text.data() + 2, 0); 168 | EXPECT_THAT(LineMessage(text, subtext, "loves quiche"), 169 | Eq(":1:3: loves quiche\nFood\n ^\n")); 170 | } 171 | 172 | TEST(LineMessage, SubtextIsEmptyAndAtVeryEnd) { 173 | StringPiece text("Food"); 174 | StringPiece subtext(text.data() + 4, 0); 175 | EXPECT_THAT(LineMessage(text, subtext, "loves quiche"), 176 | Eq(":1:5: loves quiche\nFood\n ^\n")); 177 | } 178 | 179 | } // namespace 180 | -------------------------------------------------------------------------------- /effcee/diagnostic.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Effcee Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef EFFCEE_DIAGNOSTIC_H 16 | #define EFFCEE_DIAGNOSTIC_H 17 | 18 | #include 19 | 20 | #include "effcee/effcee.h" 21 | 22 | namespace effcee { 23 | 24 | // A Diagnostic contains a Result::Status value and can accumulate message 25 | // values via operator<<. It is convertible to a Result object containing the 26 | // status and the stringified message. 27 | class Diagnostic { 28 | public: 29 | explicit Diagnostic(Result::Status status) : status_(status) {} 30 | 31 | // Copy constructor. 32 | Diagnostic(const Diagnostic& other) : status_(other.status_), message_() { 33 | // We can't move an ostringstream. As a fallback, we'd like to use the 34 | // std::ostringstream(std::string init_string) constructor. However, that 35 | // initial string disappears inexplicably the first time we shift onto 36 | // the |message_| member. So fall back further and use the default 37 | // constructor and later use an explicit shift. 38 | message_ << other.message_.str(); 39 | } 40 | 41 | // Appends the given value to the accumulated message. 42 | template 43 | Diagnostic& operator<<(const T& value) { 44 | message_ << value; 45 | return *this; 46 | } 47 | 48 | // Converts this object to a result value containing the stored status and a 49 | // stringified copy of the message. 50 | operator Result() const { return Result(status_, message_.str()); } 51 | 52 | private: 53 | Result::Status status_; 54 | std::ostringstream message_; 55 | }; 56 | 57 | } // namespace effcee 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /effcee/diagnostic_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Effcee Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "gmock/gmock.h" 16 | 17 | #include "diagnostic.h" 18 | 19 | namespace { 20 | 21 | using effcee::Diagnostic; 22 | using effcee::Result; 23 | using testing::Eq; 24 | 25 | using Status = effcee::Result::Status; 26 | 27 | // Check conversion preserves status. 28 | TEST(Diagnostic, ConvertsToResultWithSameOkStatus) { 29 | const Diagnostic d(Status::Ok); 30 | const Result r(d); 31 | EXPECT_THAT(r.status(), Eq(Status::Ok)); 32 | } 33 | 34 | TEST(Diagnostic, ConvertsToResultWithSameFailStatus) { 35 | const Diagnostic d(Status::Fail); 36 | const Result r(d); 37 | EXPECT_THAT(r.status(), Eq(Status::Fail)); 38 | } 39 | 40 | // Check conversion, with messages. 41 | 42 | TEST(Diagnostic, MessageDefaultsToEmpty) { 43 | const Diagnostic d(Status::Ok); 44 | const Result r(d); 45 | EXPECT_THAT(r.message(), Eq("")); 46 | } 47 | 48 | TEST(Diagnostic, MessageAccumulatesValuesOfDifferentTypes) { 49 | Diagnostic d(Status::Ok); 50 | d << "hello" << ' ' << 42 << " and " << 32u << " and " << 1.25; 51 | const Result r(d); 52 | EXPECT_THAT(r.message(), Eq("hello 42 and 32 and 1.25")); 53 | } 54 | 55 | // Check copying 56 | 57 | TEST(Diagnostic, CopyRetainsOriginalMessage) { 58 | Diagnostic d(Status::Ok); 59 | d << "hello"; 60 | Diagnostic d2 = d; 61 | const Result r(d2); 62 | EXPECT_THAT(r.message(), Eq("hello")); 63 | } 64 | 65 | TEST(Diagnostic, ShiftOnCopyAppendsToOriginalMessage) { 66 | Diagnostic d(Status::Ok); 67 | d << "hello"; 68 | Diagnostic d2 = d; 69 | d2 << " world"; 70 | const Result r(d2); 71 | EXPECT_THAT(r.message(), Eq("hello world")); 72 | } 73 | 74 | 75 | } // namespace 76 | -------------------------------------------------------------------------------- /effcee/effcee.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Effcee Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef EFFCEE_EFFCEE_H 16 | #define EFFCEE_EFFCEE_H 17 | 18 | #include 19 | #include "re2/re2.h" 20 | 21 | namespace effcee { 22 | 23 | // TODO(dneto): Provide a check language tutorial / manual. 24 | 25 | // This does not implement the equivalents of FileCheck options: 26 | // --match-full-lines 27 | // --strict-whitespace 28 | // --implicit-check-not 29 | // --enable-var-scope 30 | 31 | using StringPiece = re2::StringPiece; 32 | 33 | // Options for matching. 34 | class Options { 35 | public: 36 | Options() 37 | : prefix_("CHECK"), input_name_(""), checks_name_("") {} 38 | 39 | // Sets rule prefix to a copy of |prefix|. Returns this object. 40 | Options& SetPrefix(StringPiece prefix) { 41 | prefix_ = std::string(prefix.begin(), prefix.end()); 42 | return *this; 43 | } 44 | const std::string& prefix() const { return prefix_; } 45 | 46 | // Sets the input name. Returns this object. 47 | // Use this for file names, for example. 48 | Options& SetInputName(StringPiece name) { 49 | input_name_ = std::string(name.begin(), name.end()); 50 | return *this; 51 | } 52 | const std::string& input_name() const { return input_name_; } 53 | 54 | // Sets the checks input name. Returns this object. 55 | // Use this for file names, for example. 56 | Options& SetChecksName(StringPiece name) { 57 | checks_name_ = std::string(name.begin(), name.end()); 58 | return *this; 59 | } 60 | const std::string& checks_name() const { return checks_name_; } 61 | 62 | private: 63 | std::string prefix_; 64 | std::string input_name_; 65 | std::string checks_name_; 66 | }; 67 | 68 | // The result of an attempted match. 69 | class Result { 70 | public: 71 | enum class Status { 72 | Ok = 0, 73 | Fail, // A failure to match 74 | BadOption, // A bad option was specified 75 | NoRules, // No rules were specified 76 | BadRule, // A bad rule was specified 77 | }; 78 | 79 | // Constructs a result with a given status. 80 | explicit Result(Status status) : status_(status) {} 81 | // Constructs a result with the given message. Keeps a copy of the message. 82 | Result(Status status, StringPiece message) 83 | : status_(status), message_({message.begin(), message.end()}) {} 84 | 85 | Status status() const { return status_; } 86 | 87 | // Returns true if the match was successful. 88 | operator bool() const { return status_ == Status::Ok; } 89 | 90 | const std::string& message() const { return message_; } 91 | 92 | // Sets the error message to a copy of |message|. Returns this object. 93 | Result& SetMessage(StringPiece message) { 94 | message_ = std::string(message.begin(), message.end()); 95 | return *this; 96 | } 97 | 98 | private: 99 | // Status code indicating success, or kind of failure. 100 | Status status_; 101 | 102 | // Message describing the failure, if any. On success, this is empty. 103 | std::string message_; 104 | }; 105 | 106 | // Returns the result of attempting to match |text| against the pattern 107 | // program in |checks|, with the given |options|. 108 | Result Match(StringPiece text, StringPiece checks, 109 | const Options& options = Options()); 110 | 111 | } // namespace effcee 112 | 113 | #endif 114 | -------------------------------------------------------------------------------- /effcee/make_unique.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Effcee Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef EFFCEE_MAKE_UNIQUE_H 16 | #define EFFCEE_MAKE_UNIQUE_H 17 | 18 | #include 19 | 20 | namespace effcee { 21 | 22 | // Constructs an object of type T and wraps it in a std::unique_ptr. 23 | // This functionality comes with C++14. The following is the standard 24 | // recipe for use with C++11. 25 | template 26 | std::unique_ptr make_unique(Args&&... args) { 27 | return std::unique_ptr(new T(std::forward(args)...)); 28 | } 29 | 30 | } // namespace effcee 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /effcee/match.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Effcee Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "check.h" 22 | #include "cursor.h" 23 | #include "diagnostic.h" 24 | #include "effcee.h" 25 | #include "to_string.h" 26 | 27 | using effcee::Check; 28 | using Status = effcee::Result::Status; 29 | using Type = effcee::Check::Type; 30 | 31 | namespace effcee { 32 | 33 | Result Match(StringPiece input, StringPiece checks, const Options& options) { 34 | const auto& parse_result = ParseChecks(checks, options); 35 | if (!parse_result.first) return parse_result.first; 36 | 37 | // A mapping from variable names to values. This is updated when a check rule 38 | // matches a variable definition. 39 | VarMapping vars; 40 | 41 | // We think of the input string as a sequence of lines that can satisfy 42 | // the checks. Walk through the rules until no unsatisfied checks are left. 43 | // We will erase a check when it has been satisifed. 44 | const CheckList& pattern = parse_result.second; 45 | assert(pattern.size() > 0); 46 | 47 | // What checks are resolved? Entry |i| is true when check |i| in the 48 | // pattern is resolved. 49 | std::vector resolved(pattern.size(), false); 50 | 51 | // The matching algorithm scans both the input and the pattern from start 52 | // to finish. At the start, all checks are unresolved. We try to match 53 | // each line in the input against the unresolved checks in a sliding window 54 | // in the pattern. When a positive check matches, we mark it as resolved. 55 | // When a negative check matches, the algorithm terminates with failure. 56 | // We mark a negative check as resolved when it is the earliest unresolved 57 | // check and the first positive check after it is resolved. 58 | 59 | // Initially the pattern window is just the first element. 60 | // |first_check| is the first unresolved check. 61 | size_t first_check = 0; 62 | const size_t num_checks = pattern.size(); 63 | 64 | // The 1-based line number of the most recent successful match. 65 | int matched_line_num = 0; 66 | 67 | // Set up a cursor to scan the input, and helpers for generating diagnostics. 68 | Cursor cursor(input); 69 | // Points to the end of the previous positive match. 70 | StringPiece previous_match_end = input.substr(0, 0); 71 | 72 | // Returns a failure diagnostic without a message.; 73 | auto fail = []() { return Diagnostic(Status::Fail); }; 74 | // Returns a string describing the filename, line, and column of a check rule, 75 | // including the text of the check rule and a caret pointing to the parameter 76 | // string. 77 | auto check_msg = [&checks, &options](StringPiece where, StringPiece message) { 78 | std::ostringstream out; 79 | out << options.checks_name() << LineMessage(checks, where, message); 80 | return out.str(); 81 | }; 82 | // Returns a string describing the filename, line, and column of an input 83 | // string position, including the full line containing the position, and a 84 | // caret pointing to the position. 85 | auto input_msg = [&input, &options](StringPiece where, StringPiece message) { 86 | std::ostringstream out; 87 | out << options.input_name() << LineMessage(input, where, message); 88 | return out.str(); 89 | }; 90 | // Returns a string describing the value of each variable use in the 91 | // given check, in the context of the |where| portion of the input line. 92 | auto var_notes = [&input_msg, &vars](StringPiece where, const Check& check) { 93 | std::ostringstream out; 94 | for (const auto& part : check.parts()) { 95 | const auto var_use = part->VarUseName(); 96 | if (!var_use.empty()) { 97 | std::ostringstream phrase; 98 | std::string var_use_str(ToString(var_use)); 99 | if (vars.find(var_use_str) != vars.end()) { 100 | phrase << "note: with variable \"" << var_use << "\" equal to \"" 101 | << vars[var_use_str] << "\""; 102 | } else { 103 | phrase << "note: uses undefined variable \"" << var_use << "\""; 104 | } 105 | out << input_msg(where, phrase.str()); 106 | } 107 | } 108 | return out.str(); 109 | }; 110 | 111 | // For each line. 112 | for (; !cursor.Exhausted(); cursor.AdvanceLine()) { 113 | // Try to match the current line against the unresolved checks. 114 | 115 | // The number of characters the cursor should advance to accommodate a 116 | // recent DAG check match. 117 | size_t deferred_advance = 0; 118 | 119 | bool scan_this_line = true; 120 | while (scan_this_line) { 121 | // Skip the initial segment of resolved checks. Slides the left end of 122 | // the pattern window toward the right. 123 | while (first_check < num_checks && resolved[first_check]) ++first_check; 124 | // We've reached the end of the pattern. Declare success. 125 | if (first_check == num_checks) return Result(Result::Status::Ok); 126 | 127 | size_t first_unresolved_dag = num_checks; 128 | size_t first_unresolved_negative = num_checks; 129 | 130 | bool resolved_something = false; 131 | 132 | for (size_t i = first_check; i < num_checks; ++i) { 133 | if (resolved[i]) continue; 134 | 135 | const Check& check = pattern[i]; 136 | 137 | if (check.type() != Type::DAG) { 138 | cursor.Advance(deferred_advance); 139 | deferred_advance = 0; 140 | } 141 | const StringPiece rest_of_line = cursor.RestOfLine(); 142 | StringPiece unconsumed = rest_of_line; 143 | StringPiece captured; 144 | 145 | if (check.Matches(&unconsumed, &captured, &vars)) { 146 | if (check.type() == Type::Not) { 147 | return fail() << input_msg(captured, 148 | "error: CHECK-NOT: string occurred!") 149 | << check_msg( 150 | check.param(), 151 | "note: CHECK-NOT: pattern specified here") 152 | << var_notes(captured, check); 153 | } 154 | 155 | if (check.type() == Type::Same && 156 | cursor.line_num() != matched_line_num) { 157 | return fail() 158 | << check_msg(check.param(), 159 | "error: CHECK-SAME: is not on the same line as " 160 | "previous match") 161 | << input_msg(captured, "note: 'next' match was here") 162 | << input_msg(previous_match_end, 163 | "note: previous match ended here"); 164 | } 165 | 166 | if (check.type() == Type::Next) { 167 | if (cursor.line_num() == matched_line_num) { 168 | return fail() 169 | << check_msg(check.param(), 170 | "error: CHECK-NEXT: is on the same line as " 171 | "previous match") 172 | << input_msg(captured, "note: 'next' match was here") 173 | << input_msg(previous_match_end, 174 | "note: previous match ended here") 175 | << var_notes(previous_match_end, check); 176 | } 177 | if (cursor.line_num() > 1 + matched_line_num) { 178 | // This must be valid since there was an intervening line. 179 | const auto non_match = 180 | Cursor(input) 181 | .Advance(previous_match_end.data() - input.data()) 182 | .AdvanceLine() 183 | .RestOfLine(); 184 | 185 | return fail() 186 | << check_msg(check.param(), 187 | "error: CHECK-NEXT: is not on the line after " 188 | "the previous match") 189 | << input_msg(captured, "note: 'next' match was here") 190 | << input_msg(previous_match_end, 191 | "note: previous match ended here") 192 | << input_msg(non_match, 193 | "note: non-matching line after previous " 194 | "match is here") 195 | << var_notes(previous_match_end, check); 196 | } 197 | } 198 | 199 | if (check.type() != Type::DAG && first_unresolved_dag < i) { 200 | return fail() 201 | << check_msg(pattern[first_unresolved_dag].param(), 202 | "error: expected string not found in input") 203 | << input_msg(previous_match_end, "note: scanning from here") 204 | << input_msg(captured, "note: next check matches here") 205 | << var_notes(previous_match_end, check); 206 | } 207 | 208 | resolved[i] = true; 209 | matched_line_num = cursor.line_num(); 210 | previous_match_end = unconsumed; 211 | resolved_something = true; 212 | 213 | // Resolve any prior negative checks that precede an unresolved DAG. 214 | for (auto j = first_unresolved_negative, 215 | limit = std::min(first_unresolved_dag, i); 216 | j < limit; ++j) { 217 | resolved[j] = true; 218 | } 219 | 220 | // Normally advance past the matched text. But DAG checks might need 221 | // to match out of order on the same line. So only advance for 222 | // non-DAG cases. 223 | 224 | const size_t advance_proposal = 225 | rest_of_line.size() - unconsumed.size(); 226 | if (check.type() == Type::DAG) { 227 | deferred_advance = std::max(deferred_advance, advance_proposal); 228 | } else { 229 | cursor.Advance(advance_proposal); 230 | } 231 | 232 | } else { 233 | // This line did not match the check. 234 | if (check.type() == Type::Not) { 235 | first_unresolved_negative = std::min(first_unresolved_negative, i); 236 | // An unresolved Not check stops the search for more DAG checks. 237 | if (first_unresolved_dag < num_checks) i = num_checks; 238 | } else if (check.type() == Type::DAG) { 239 | first_unresolved_dag = std::min(first_unresolved_dag, i); 240 | } else { 241 | // An unresolved non-DAG check check stops this pass over the 242 | // checks. 243 | i = num_checks; 244 | } 245 | } 246 | } 247 | scan_this_line = resolved_something; 248 | } 249 | } 250 | 251 | // Fail if there are any unresolved positive checks. 252 | for (auto i = first_check; i < num_checks; ++i) { 253 | if (resolved[i]) continue; 254 | const auto check = pattern[i]; 255 | if (check.type() == Type::Not) continue; 256 | 257 | return fail() << check_msg(check.param(), 258 | "error: expected string not found in input") 259 | << input_msg(previous_match_end, "note: scanning from here") 260 | << var_notes(previous_match_end, check); 261 | } 262 | 263 | return Result(Result::Status::Ok); 264 | } 265 | } // namespace effcee 266 | -------------------------------------------------------------------------------- /effcee/match_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Effcee Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "gmock/gmock.h" 16 | 17 | #include "effcee.h" 18 | 19 | namespace { 20 | 21 | using effcee::Match; 22 | using effcee::Options; 23 | using ::testing::Eq; 24 | using ::testing::HasSubstr; 25 | 26 | const char* kNotFound = "error: expected string not found in input"; 27 | const char* kMissedSame = 28 | "error: CHECK-SAME: is not on the same line as previous match"; 29 | const char* kNextOnSame = 30 | "error: CHECK-NEXT: is on the same line as previous match"; 31 | const char* kNextTooLate = 32 | "error: CHECK-NEXT: is not on the line after the previous match"; 33 | const char* kNotStrFound = "error: CHECK-NOT: string occurred!"; 34 | 35 | // Match free function 36 | 37 | TEST(Match, FreeFunctionLinks) { 38 | Match("", ""); 39 | Match("", "", effcee::Options()); 40 | } 41 | 42 | // Simple checks 43 | 44 | TEST(Match, OneSimpleCheckPass) { 45 | const auto result = Match("Hello", "CHECK: Hello"); 46 | EXPECT_TRUE(result) << result.message(); 47 | } 48 | 49 | TEST(Match, OneSimpleCheckFail) { 50 | const auto result = Match("World", "CHECK: Hello"); 51 | EXPECT_FALSE(result) << result.message(); 52 | EXPECT_THAT(result.message(), HasSubstr(kNotFound)); 53 | EXPECT_THAT(result.message(), HasSubstr("CHECK: Hello")); 54 | } 55 | 56 | TEST(Match, TwoSimpleChecksPass) { 57 | const auto result = Match("Hello\nWorld", "CHECK: Hello\nCHECK: World"); 58 | EXPECT_TRUE(result) << result.message(); 59 | } 60 | 61 | TEST(Match, RepeatedCheckFails) { 62 | const auto result = Match("Hello\nWorld", "CHECK: Hello\nCHECK: Hello"); 63 | EXPECT_FALSE(result) << result.message(); 64 | EXPECT_THAT(result.message(), HasSubstr(kNotFound)); 65 | } 66 | 67 | TEST(Match, TwoSimpleChecksPassWithSurroundingText) { 68 | const auto input = R"(Say 69 | Hello 70 | World 71 | Today)"; 72 | const auto result = Match(input, "CHECK: Hello\nCHECK: World"); 73 | EXPECT_TRUE(result) << result.message(); 74 | } 75 | 76 | TEST(Match, TwoSimpleChecksPassWithInterveningText) { 77 | const auto input = R"(Hello 78 | Between 79 | World)"; 80 | const auto result = Match(input, "CHECK: Hello\nCHECK: World"); 81 | EXPECT_TRUE(result) << result.message(); 82 | } 83 | 84 | TEST(Match, TwoSimpleChecksPassWhenInSequenceSameLine) { 85 | const auto result = Match("HelloWorld", "CHECK: Hello\nCHECK: World"); 86 | EXPECT_TRUE(result) << result.message(); 87 | } 88 | 89 | TEST(Match, TwoSimpleChecksFailWhenReversed) { 90 | const auto result = Match("HelloWorld", "CHECK: World\nCHECK: Hello"); 91 | EXPECT_FALSE(result) << result.message(); 92 | EXPECT_THAT(result.message(), HasSubstr(kNotFound)); 93 | EXPECT_THAT(result.message(), HasSubstr("CHECK: Hello")); 94 | } 95 | 96 | TEST(Match, SimpleThenSamePasses) { 97 | const auto result = Match("HelloWorld", "CHECK: Hello\nCHECK-SAME: World"); 98 | EXPECT_TRUE(result) << result.message(); 99 | } 100 | 101 | TEST(Match, SimpleThenSamePassesWithInterveningOnSameLine) { 102 | const auto result = Match("Hello...World", "CHECK: Hello\nCHECK-SAME: World"); 103 | EXPECT_TRUE(result) << result.message(); 104 | } 105 | 106 | TEST(Match, SimpleThenSameFailsIfOnNextLine) { 107 | const auto result = Match("Hello\nWorld", "CHECK: Hello\nCHECK-SAME: World"); 108 | EXPECT_FALSE(result) << result.message(); 109 | EXPECT_THAT(result.message(),HasSubstr(kMissedSame)); 110 | EXPECT_THAT(result.message(), HasSubstr("CHECK-SAME: World")); 111 | } 112 | 113 | TEST(Match, SimpleThenSameFailsIfOnMuchLaterLine) { 114 | const auto result = 115 | Match("Hello\n\nz\n\nWorld", "CHECK: Hello\nCHECK-SAME: World"); 116 | EXPECT_FALSE(result) << result.message(); 117 | EXPECT_THAT(result.message(), HasSubstr(kMissedSame)); 118 | EXPECT_THAT(result.message(), HasSubstr("CHECK-SAME: World")); 119 | } 120 | 121 | TEST(Match, SimpleThenSameFailsIfNeverMatched) { 122 | const auto result = Match("Hello\nHome", "CHECK: Hello\nCHECK-SAME: World"); 123 | EXPECT_FALSE(result) << result.message(); 124 | EXPECT_THAT(result.message(), HasSubstr(kNotFound)); 125 | EXPECT_THAT(result.message(), HasSubstr("CHECK-SAME: World")); 126 | } 127 | 128 | TEST(Match, SimpleThenNextOnSameLineFails) { 129 | const auto result = Match("HelloWorld", "CHECK: Hello\nCHECK-NEXT: World"); 130 | EXPECT_FALSE(result); 131 | EXPECT_THAT(result.message(), HasSubstr(kNextOnSame)); 132 | EXPECT_THAT(result.message(), HasSubstr("CHECK-NEXT: World")); 133 | } 134 | 135 | TEST(Match, SimpleThenNextPassesIfOnNextLine) { 136 | const auto result = Match("Hello\nWorld", "CHECK: Hello\nCHECK-NEXT: World"); 137 | EXPECT_TRUE(result) << result.message(); 138 | } 139 | 140 | TEST(Match, SimpleThenNextFailsIfOnAfterNextLine) { 141 | const auto result = Match("Hello\nfoo\nWorld", "CHECK: Hello\nCHECK-NEXT: World"); 142 | EXPECT_THAT(result.message(), HasSubstr(kNextTooLate)); 143 | EXPECT_THAT(result.message(), HasSubstr("CHECK-NEXT: World")); 144 | } 145 | 146 | TEST(Match, SimpleThenNextFailsIfNeverMatched) { 147 | const auto result = 148 | Match("Hello\nHome", "CHECK: Hello\nCHECK-NEXT: World"); 149 | EXPECT_FALSE(result) << result.message(); 150 | EXPECT_THAT(result.message(), HasSubstr(kNotFound)); 151 | EXPECT_THAT(result.message(), HasSubstr("CHECK-NEXT: World")); 152 | } 153 | 154 | // TODO: CHECK-NOT 155 | 156 | TEST(Match, AloneNotNeverSeenPasses) { 157 | const auto result = Match("Hello", "CHECK-NOT: Borg"); 158 | EXPECT_TRUE(result); 159 | } 160 | 161 | TEST(Match, LeadingNotNeverSeenPasses) { 162 | const auto result = Match("Hello", "CHECK-NOT: Borg\nCHECK: Hello"); 163 | EXPECT_TRUE(result); 164 | } 165 | 166 | TEST(Match, BetweenNotNeverSeenPasses) { 167 | const auto result = 168 | Match("HelloWorld", "CHECK: Hello\nCHECK-NOT: Borg\nCHECK: World"); 169 | EXPECT_TRUE(result); 170 | } 171 | 172 | TEST(Match, BetweenNotDotsNeverSeenPasses) { 173 | // The before and after matches occur on the same line. 174 | const auto result = 175 | Match("Hello...World", "CHECK: Hello\nCHECK-NOT: Borg\nCHECK: World"); 176 | EXPECT_TRUE(result); 177 | } 178 | 179 | TEST(Match, BetweenNotLinesNeverSeenPasses) { 180 | // The before and after matches occur on different lines. 181 | const auto result = 182 | Match("Hello\nz\nWorld", "CHECK: Hello\nCHECK-NOT: Borg\nCHECK: World"); 183 | EXPECT_TRUE(result); 184 | } 185 | 186 | TEST(Match, NotBetweenMatchesPasses) { 187 | const auto result = 188 | Match("Hello\nWorld\nBorg\n", "CHECK: Hello\nCHECK-NOT: Borg\nCHECK: World"); 189 | EXPECT_TRUE(result); 190 | } 191 | 192 | TEST(Match, NotBeforeFirstMatchPasses) { 193 | const auto result = 194 | Match("Hello\nWorld\nBorg\n", "CHECK-NOT: World\nCHECK: Hello"); 195 | EXPECT_TRUE(result); 196 | } 197 | 198 | TEST(Match, NotAfterLastMatchPasses) { 199 | const auto result = 200 | Match("Hello\nWorld\nBorg\n", "CHECK: World\nCHECK-NOT: Hello"); 201 | EXPECT_TRUE(result); 202 | } 203 | 204 | TEST(Match, NotBeforeFirstMatchFails) { 205 | const auto result = 206 | Match("Hello\nWorld\n", "CHECK-NOT: Hello\nCHECK: World"); 207 | EXPECT_FALSE(result); 208 | } 209 | 210 | TEST(Match, NotBetweenMatchesFails) { 211 | const auto result = 212 | Match("Hello\nWorld\nBorg\n", "CHECK: Hello\nCHECK-NOT: World\nCHECK: Borg"); 213 | EXPECT_FALSE(result); 214 | } 215 | 216 | TEST(Match, NotAfterLastMatchFails) { 217 | const auto result = 218 | Match("Hello\nWorld\n", "CHECK: Hello\nCHECK-NOT: World"); 219 | EXPECT_FALSE(result); 220 | } 221 | 222 | TEST(Match, TrailingNotNeverSeenPasses) { 223 | const auto result = Match("Hello", "CHECK: Hello\nCHECK-NOT: Borg"); 224 | EXPECT_TRUE(result); 225 | } 226 | 227 | TEST(Match, AloneNotSeenFails) { 228 | const auto result = Match("Borg", "CHECK-NOT: Borg"); 229 | EXPECT_FALSE(result); 230 | EXPECT_THAT(result.message(), HasSubstr(kNotStrFound)); 231 | EXPECT_THAT(result.message(), HasSubstr("CHECK-NOT: Borg")); 232 | } 233 | 234 | TEST(Match, LeadingNotSeenFails) { 235 | const auto result = Match("Borg", "CHECK-NOT: Borg\nCHECK: Hello"); 236 | EXPECT_FALSE(result); 237 | EXPECT_THAT(result.message(), HasSubstr(kNotStrFound)); 238 | EXPECT_THAT(result.message(), HasSubstr("CHECK-NOT: Borg")); 239 | } 240 | 241 | TEST(Match, BetweenNotSeenFails) { 242 | const auto result = 243 | Match("HelloBorgWorld", "CHECK: Hello\nCHECK-NOT: Borg\nCHECK: World"); 244 | EXPECT_FALSE(result); 245 | EXPECT_THAT(result.message(), HasSubstr(kNotStrFound)); 246 | EXPECT_THAT(result.message(), HasSubstr("CHECK-NOT: Borg")); 247 | } 248 | 249 | TEST(Match, BetweenNotDotsSeenFails) { 250 | const auto result = 251 | Match("Hello.Borg.World", "CHECK: Hello\nCHECK-NOT: Borg\nCHECK: World"); 252 | EXPECT_FALSE(result); 253 | EXPECT_THAT(result.message(), HasSubstr(kNotStrFound)); 254 | EXPECT_THAT(result.message(), HasSubstr("CHECK-NOT: Borg")); 255 | } 256 | 257 | TEST(Match, BetweenNotLinesSeenFails) { 258 | const auto result = Match("Hello\nBorg\nWorld", 259 | "CHECK: Hello\nCHECK-NOT: Borg\nCHECK: World"); 260 | EXPECT_FALSE(result); 261 | EXPECT_THAT(result.message(), HasSubstr(kNotStrFound)); 262 | EXPECT_THAT(result.message(), HasSubstr("CHECK-NOT: Borg")); 263 | } 264 | 265 | TEST(Match, TrailingNotSeenFails) { 266 | const auto result = Match("HelloBorg", "CHECK: Hello\nCHECK-NOT: Borg"); 267 | EXPECT_FALSE(result); 268 | EXPECT_THAT(result.message(), HasSubstr(kNotStrFound)); 269 | EXPECT_THAT(result.message(), HasSubstr("CHECK-NOT: Borg")); 270 | } 271 | 272 | // WIP: CHECK-LABEL 273 | 274 | TEST(Match, OneLabelCheckPass) { 275 | const auto result = Match("Hello", "CHECK-LABEL: Hello"); 276 | EXPECT_TRUE(result) << result.message(); 277 | } 278 | 279 | TEST(Match, OneLabelCheckFail) { 280 | const auto result = Match("World", "CHECK-LABEL: Hello"); 281 | EXPECT_FALSE(result) << result.message(); 282 | EXPECT_THAT(result.message(), HasSubstr(kNotFound)); 283 | EXPECT_THAT(result.message(), HasSubstr("CHECK-LABEL: Hello")); 284 | } 285 | 286 | TEST(Match, TwoLabelChecksPass) { 287 | const auto result = 288 | Match("Hello\nWorld", "CHECK-LABEL: Hello\nCHECK-LABEL: World"); 289 | EXPECT_TRUE(result) << result.message(); 290 | } 291 | 292 | TEST(Match, TwoLabelChecksPassWithSurroundingText) { 293 | const auto input = R"(Say 294 | Hello 295 | World 296 | Today)"; 297 | const auto result = Match(input, "CHECK-LABEL: Hello\nCHECK-LABEL: World"); 298 | EXPECT_TRUE(result) << result.message(); 299 | } 300 | 301 | TEST(Match, TwoLabelChecksPassWithInterveningText) { 302 | const auto input = R"(Hello 303 | Between 304 | World)"; 305 | const auto result = Match(input, "CHECK-LABEL: Hello\nCHECK-LABEL: World"); 306 | EXPECT_TRUE(result) << result.message(); 307 | } 308 | 309 | TEST(Match, TwoLabelChecksPassWhenInSequenceSameLine) { 310 | const auto result = 311 | Match("HelloWorld", "CHECK-LABEL: Hello\nCHECK-LABEL: World"); 312 | EXPECT_TRUE(result) << result.message(); 313 | } 314 | 315 | TEST(Match, TwoLabelChecksFailWhenReversed) { 316 | const auto result = 317 | Match("HelloWorld", "CHECK-LABEL: World\nCHECK-LABEL: Hello"); 318 | EXPECT_FALSE(result) << result.message(); 319 | EXPECT_THAT(result.message(), HasSubstr(kNotFound)); 320 | EXPECT_THAT(result.message(), HasSubstr("CHECK-LABEL: Hello")); 321 | } 322 | 323 | // WIP: Mixture of Simple and Label checks 324 | 325 | TEST(Match, SimpleAndLabelChecksPass) { 326 | const auto result = Match("Hello\nWorld", "CHECK: Hello\nCHECK-LABEL: World"); 327 | EXPECT_TRUE(result) << result.message(); 328 | } 329 | 330 | TEST(Match, LabelAndSimpleChecksPass) { 331 | const auto result = Match("Hello\nWorld", "CHECK-LABEL: Hello\nCHECK: World"); 332 | EXPECT_TRUE(result) << result.message(); 333 | } 334 | 335 | TEST(Match, SimpleAndLabelChecksFails) { 336 | const auto result = Match("Hello\nWorld", "CHECK: Hello\nCHECK-LABEL: Band"); 337 | EXPECT_FALSE(result) << result.message(); 338 | EXPECT_THAT(result.message(), HasSubstr(kNotFound)); 339 | EXPECT_THAT(result.message(), HasSubstr("CHECK-LABEL: Band")); 340 | } 341 | 342 | TEST(Match, LabelAndSimpleChecksFails) { 343 | const auto result = Match("Hello\nWorld", "CHECK-LABEL: Hello\nCHECK: Band"); 344 | EXPECT_FALSE(result) << result.message(); 345 | EXPECT_THAT(result.message(), HasSubstr(kNotFound)); 346 | EXPECT_THAT(result.message(), HasSubstr("CHECK: Band")); 347 | } 348 | 349 | // DAG checks: Part 1: Tests simlar to simple checks tests 350 | 351 | TEST(Match, OneDAGCheckPass) { 352 | const auto result = Match("Hello", "CHECK-DAG: Hello"); 353 | EXPECT_TRUE(result) << result.message(); 354 | } 355 | 356 | TEST(Match, OneDAGCheckFail) { 357 | const auto result = Match("World", "CHECK-DAG: Hello"); 358 | EXPECT_FALSE(result) << result.message(); 359 | EXPECT_THAT(result.message(), HasSubstr(kNotFound)); 360 | EXPECT_THAT(result.message(), HasSubstr("CHECK-DAG: Hello")); 361 | } 362 | 363 | TEST(Match, TwoDAGChecksPass) { 364 | const auto result = Match("Hello\nWorld", "CHECK-DAG: Hello\nCHECK-DAG: World"); 365 | EXPECT_TRUE(result) << result.message(); 366 | } 367 | 368 | TEST(Match, TwoDAGChecksPassWithSurroundingText) { 369 | const auto input = R"(Say 370 | Hello 371 | World 372 | Today)"; 373 | const auto result = Match(input, "CHECK-DAG: Hello\nCHECK-DAG: World"); 374 | EXPECT_TRUE(result) << result.message(); 375 | } 376 | 377 | TEST(Match, TwoDAGChecksPassWithInterveningText) { 378 | const auto input = R"(Hello 379 | Between 380 | World)"; 381 | const auto result = Match(input, "CHECK-DAG: Hello\nCHECK-DAG: World"); 382 | EXPECT_TRUE(result) << result.message(); 383 | } 384 | 385 | TEST(Match, TwoDAGChecksPassWhenInSequenceSameLine) { 386 | const auto result = Match("HelloWorld", "CHECK-DAG: Hello\nCHECK-DAG: World"); 387 | EXPECT_TRUE(result) << result.message(); 388 | } 389 | 390 | TEST(Match, DAGThenSamePasses) { 391 | const auto result = Match("HelloWorld", "CHECK-DAG: Hello\nCHECK-SAME: World"); 392 | EXPECT_TRUE(result) << result.message(); 393 | } 394 | 395 | TEST(Match, DAGThenSamePassesWithInterveningOnSameLine) { 396 | const auto result = Match("Hello...World", "CHECK-DAG: Hello\nCHECK-SAME: World"); 397 | EXPECT_TRUE(result) << result.message(); 398 | } 399 | 400 | TEST(Match, DAGThenSameFailsIfOnNextLine) { 401 | const auto result = Match("Hello\nWorld", "CHECK-DAG: Hello\nCHECK-SAME: World"); 402 | EXPECT_FALSE(result) << result.message(); 403 | EXPECT_THAT(result.message(), HasSubstr(kMissedSame)); 404 | EXPECT_THAT(result.message(), HasSubstr("CHECK-SAME: World")); 405 | } 406 | 407 | TEST(Match, DAGThenSameFailsIfOnMuchLaterLine) { 408 | const auto result = 409 | Match("Hello\n\nz\n\nWorld", "CHECK-DAG: Hello\nCHECK-SAME: World"); 410 | EXPECT_FALSE(result) << result.message(); 411 | EXPECT_THAT(result.message(), HasSubstr(kMissedSame)); 412 | EXPECT_THAT(result.message(), HasSubstr("CHECK-SAME: World")); 413 | } 414 | 415 | TEST(Match, DAGThenSameFailsIfNeverMatched) { 416 | const auto result = Match("Hello\nHome", "CHECK-DAG: Hello\nCHECK-SAME: World"); 417 | EXPECT_FALSE(result) << result.message(); 418 | EXPECT_THAT(result.message(), HasSubstr(kNotFound)); 419 | EXPECT_THAT(result.message(), HasSubstr("CHECK-SAME: World")); 420 | } 421 | 422 | TEST(Match, DAGThenNextOnSameLineFails) { 423 | const auto result = Match("HelloWorld", "CHECK-DAG: Hello\nCHECK-NEXT: World"); 424 | EXPECT_FALSE(result); 425 | EXPECT_THAT(result.message(), HasSubstr(kNextOnSame)); 426 | EXPECT_THAT(result.message(), HasSubstr("CHECK-NEXT: World")); 427 | } 428 | 429 | TEST(Match, DAGThenNextPassesIfOnNextLine) { 430 | const auto result = Match("Hello\nWorld", "CHECK-DAG: Hello\nCHECK-NEXT: World"); 431 | EXPECT_TRUE(result) << result.message(); 432 | } 433 | 434 | TEST(Match, DAGThenNextPassesIfOnAfterNextLine) { 435 | const auto result = Match("Hello\nWorld", "CHECK-DAG: Hello\nCHECK-NEXT: World"); 436 | EXPECT_TRUE(result) << result.message(); 437 | } 438 | 439 | TEST(Match, DAGThenNextFailsIfNeverMatched) { 440 | const auto result = 441 | Match("Hello\nHome", "CHECK-DAG: Hello\nCHECK-NEXT: World"); 442 | EXPECT_FALSE(result) << result.message(); 443 | EXPECT_THAT(result.message(), HasSubstr(kNotFound)); 444 | EXPECT_THAT(result.message(), HasSubstr("CHECK-NEXT: World")); 445 | } 446 | 447 | // DAG checks: Part 2: Out of order matching 448 | 449 | TEST(Match, TwoDAGMatchedOutOfOrderPasses) { 450 | const auto result = Match("Hello\nWorld", "CHECK-DAG: World\nCHECK-DAG: Hello"); 451 | EXPECT_TRUE(result) << result.message(); 452 | } 453 | 454 | TEST(Match, ThreeDAGMatchedOutOfOrderPasses) { 455 | const auto result = 456 | Match("Hello\nWorld\nNow", 457 | "CHECK-DAG: Now\nCHECK-DAG: World\nCHECK-DAG: Hello"); 458 | EXPECT_TRUE(result) << result.message(); 459 | } 460 | 461 | TEST(Match, TwoDAGChecksPassWhenReversedMatchingSameLine) { 462 | const auto result = Match("HelloWorld", "CHECK-DAG: World\nCHECK-DAG: Hello"); 463 | EXPECT_TRUE(result) << result.message(); 464 | } 465 | 466 | TEST(Match, DAGChecksGreedilyConsumeInput) { 467 | const auto result = 468 | Match("Hello\nBlocker\nWorld\n", 469 | "CHECK-DAG: Hello\nCHECK-DAG: World\nCHECK: Blocker"); 470 | EXPECT_FALSE(result) << result.message(); 471 | EXPECT_THAT(result.message(), HasSubstr(kNotFound)); 472 | EXPECT_THAT(result.message(), HasSubstr("CHECK-DAG: World")); 473 | } 474 | 475 | // DAG checks: Part 3: Interaction with Not checks 476 | 477 | TEST(Match, DAGsAreSeparatedByNot) { 478 | // In this case the search for "Before" consumes the entire input. 479 | const auto result = 480 | Match("After\nBlocker\nBefore\n", 481 | "CHECK-DAG: Before\nCHECK-NOT: nothing\nCHECK-DAG: After"); 482 | EXPECT_FALSE(result) << result.message(); 483 | EXPECT_THAT(result.message(), HasSubstr(kNotFound)); 484 | EXPECT_THAT(result.message(), HasSubstr("CHECK-DAG: After")); 485 | } 486 | 487 | TEST(Match, TwoDAGsAreSeparatedByNot) { 488 | const auto result = Match("After\nApres\nBlocker\nBefore\nAnte", 489 | "CHECK-DAG: Ante\nCHECK-DAG: Before\nCHECK-NOT: " 490 | "nothing\nCHECK-DAG: Apres\nCHECK-DAG: After"); 491 | EXPECT_FALSE(result) << result.message(); 492 | EXPECT_THAT(result.message(), HasSubstr(kNotFound)); 493 | EXPECT_THAT(result.message(), HasSubstr("CHECK-DAG: Apres")); 494 | } 495 | 496 | // DAG checks: Part 4: Interaction with simple checks 497 | 498 | TEST(Match, DAGsAreTerminatedBySimple) { 499 | const auto result = 500 | Match("After\nsimple\nBefore\n", 501 | "CHECK-DAG: Before\nCHECK: simple\nCHECK-DAG: After"); 502 | EXPECT_FALSE(result) << result.message(); 503 | EXPECT_THAT(result.message(), HasSubstr(kNotFound)); 504 | EXPECT_THAT(result.message(), HasSubstr("CHECK-DAG: Before")); 505 | } 506 | 507 | TEST(Match, TwoDAGsAreTerminatedBySimple) { 508 | const auto result = Match("After\nApres\nBlocker\nBefore\nAnte", 509 | "CHECK-DAG: Ante\nCHECK-DAG: Before\nCHECK: " 510 | "Blocker\nCHECK-DAG: Apres\nCHECK-DAG: After"); 511 | EXPECT_FALSE(result) << result.message(); 512 | EXPECT_THAT(result.message(), HasSubstr(kNotFound)); 513 | EXPECT_THAT(result.message(), HasSubstr("CHECK-DAG: Ante")); 514 | } 515 | 516 | // Test detailed message text 517 | 518 | TEST(Match, MessageStringNotFoundWhenNeverMatchedAnything) { 519 | const char* input = R"(Begin 520 | Hello 521 | World)"; 522 | const char* checks = R"( 523 | Hello 524 | ; CHECK: Needle 525 | )"; 526 | const char* expected = R"(chklist:3:13: error: expected string not found in input 527 | ; CHECK: Needle 528 | ^ 529 | myin.txt:1:1: note: scanning from here 530 | Begin 531 | ^ 532 | )"; 533 | const auto result = 534 | Match(input, checks, 535 | Options().SetInputName("myin.txt").SetChecksName("chklist")); 536 | EXPECT_FALSE(result); 537 | EXPECT_THAT(result.message(), Eq(expected)) << result.message(); 538 | } 539 | 540 | TEST(Match, MessageStringNotFoundAfterInitialMatch) { 541 | const char* input = R"(Begin 542 | Hello 543 | World)"; 544 | const char* checks = R"( 545 | Hello 546 | ; CHECK-LABEL: Hel 547 | ; CHECK: Needle 548 | )"; 549 | const char* expected = R"(chklist:4:13: error: expected string not found in input 550 | ; CHECK: Needle 551 | ^ 552 | myin.txt:2:4: note: scanning from here 553 | Hello 554 | ^ 555 | )"; 556 | const auto result = 557 | Match(input, checks, 558 | Options().SetInputName("myin.txt").SetChecksName("chklist")); 559 | EXPECT_FALSE(result); 560 | EXPECT_THAT(result.message(), Eq(expected)) << result.message(); 561 | } 562 | 563 | TEST(Match, MessageCheckNotStringFoundAtStart) { 564 | const auto result = 565 | Match(" Cheese", "CHECK-NOT: Cheese", 566 | Options().SetInputName("in").SetChecksName("checks")); 567 | EXPECT_FALSE(result); 568 | const char* expected = R"(in:1:3: error: CHECK-NOT: string occurred! 569 | Cheese 570 | ^ 571 | checks:1:12: note: CHECK-NOT: pattern specified here 572 | CHECK-NOT: Cheese 573 | ^ 574 | )"; 575 | EXPECT_THAT(result.message(), Eq(expected)) << result.message(); 576 | } 577 | 578 | TEST(Match, MessageCheckNotStringFoundAfterInitialMatch) { 579 | const auto result = 580 | Match("Cream Cheese", "CHECK: Cream\nCHECK-NOT: Cheese", 581 | Options().SetInputName("in").SetChecksName("checks")); 582 | EXPECT_FALSE(result); 583 | const char* expected = R"(in:1:10: error: CHECK-NOT: string occurred! 584 | Cream Cheese 585 | ^ 586 | checks:2:12: note: CHECK-NOT: pattern specified here 587 | CHECK-NOT: Cheese 588 | ^ 589 | )"; 590 | EXPECT_THAT(result.message(), Eq(expected)) << result.message(); 591 | } 592 | 593 | TEST(Match, MessageCheckSameFails) { 594 | const char* input = R"( 595 | Bees 596 | Make 597 | Delicious Honey 598 | )"; 599 | const char* checks = R"( 600 | CHECK: Make 601 | CHECK-SAME: Honey 602 | )"; 603 | 604 | const auto result = Match( 605 | input, checks, Options().SetInputName("in").SetChecksName("checks")); 606 | EXPECT_FALSE(result); 607 | const char* expected = R"(checks:3:13: error: CHECK-SAME: is not on the same line as previous match 608 | CHECK-SAME: Honey 609 | ^ 610 | in:4:11: note: 'next' match was here 611 | Delicious Honey 612 | ^ 613 | in:3:5: note: previous match ended here 614 | Make 615 | ^ 616 | )"; 617 | EXPECT_THAT(result.message(), Eq(expected)) << result.message(); 618 | } 619 | 620 | TEST(Match, MessageCheckNextFailsSinceOnSameLine) { 621 | const char* input = R"( 622 | Bees 623 | Make 624 | Delicious Honey 625 | )"; 626 | const char* checks = R"( 627 | CHECK: Bees 628 | CHECK-NEXT: Honey 629 | )"; 630 | 631 | const auto result = Match( 632 | input, checks, Options().SetInputName("in").SetChecksName("checks")); 633 | EXPECT_FALSE(result); 634 | const char* expected = R"(checks:3:13: error: CHECK-NEXT: is not on the line after the previous match 635 | CHECK-NEXT: Honey 636 | ^ 637 | in:4:11: note: 'next' match was here 638 | Delicious Honey 639 | ^ 640 | in:2:5: note: previous match ended here 641 | Bees 642 | ^ 643 | in:3:1: note: non-matching line after previous match is here 644 | Make 645 | ^ 646 | )"; 647 | EXPECT_THAT(result.message(), Eq(expected)) << result.message(); 648 | } 649 | 650 | TEST(Match, MessageCheckNextFailsSinceLaterLine) { 651 | const char* input = R"( 652 | Bees Make Delicious Honey 653 | )"; 654 | const char* checks = R"( 655 | CHECK: Make 656 | CHECK-NEXT: Honey 657 | )"; 658 | 659 | const auto result = Match( 660 | input, checks, Options().SetInputName("in").SetChecksName("checks")); 661 | EXPECT_FALSE(result); 662 | const char* expected = R"(checks:3:13: error: CHECK-NEXT: is on the same line as previous match 663 | CHECK-NEXT: Honey 664 | ^ 665 | in:2:21: note: 'next' match was here 666 | Bees Make Delicious Honey 667 | ^ 668 | in:2:10: note: previous match ended here 669 | Bees Make Delicious Honey 670 | ^ 671 | )"; 672 | EXPECT_THAT(result.message(), Eq(expected)) << result.message(); 673 | } 674 | 675 | TEST(Match, MessageUnresolvedDAG) { 676 | const char* input = R"( 677 | Bees 678 | Make 679 | Delicious Honey 680 | )"; 681 | const char* checks = R"( 682 | CHECK: ees 683 | CHECK-DAG: Flowers 684 | CHECK: Honey 685 | )"; 686 | 687 | const auto result = Match( 688 | input, checks, Options().SetInputName("in").SetChecksName("checks")); 689 | EXPECT_FALSE(result); 690 | const char* expected = R"(checks:3:12: error: expected string not found in input 691 | CHECK-DAG: Flowers 692 | ^ 693 | in:2:5: note: scanning from here 694 | Bees 695 | ^ 696 | in:4:11: note: next check matches here 697 | Delicious Honey 698 | ^ 699 | )"; 700 | EXPECT_THAT(result.message(), Eq(expected)) << result.message(); 701 | } 702 | 703 | 704 | // Regexp 705 | 706 | TEST(Match, CheckRegexPass) { 707 | const auto result = Match("Hello", "CHECK: He{{ll}}o"); 708 | EXPECT_TRUE(result) << result.message(); 709 | } 710 | 711 | TEST(Match, CheckRegexWithFalseStartPass) { 712 | // This examples has three false starts. That is, we match the first 713 | // few parts of the pattern before we finally match it. 714 | const auto result = Match("He Hel Hell Hello Helloo", "CHECK: He{{ll}}oo"); 715 | EXPECT_TRUE(result) << result.message(); 716 | } 717 | 718 | TEST(Match, CheckRegexWithRangePass) { 719 | const auto result = Match("Hello", "CHECK: He{{[a-z]+}}o"); 720 | EXPECT_TRUE(result) << result.message(); 721 | } 722 | 723 | TEST(Match, CheckRegexMatchesEmptyPass) { 724 | const auto result = Match("Heo", "CHECK: He{{[a-z]*}}o"); 725 | EXPECT_TRUE(result) << result.message(); 726 | } 727 | 728 | TEST(Match, CheckThreeRegexPass) { 729 | // This proves that we parsed the check correctly, finding matching pairs 730 | // of regexp delimiters {{ and }}. 731 | const auto result = Match("Hello World", "CHECK: He{{[a-z]+}}o{{ +}}{{[Ww]}}orld"); 732 | EXPECT_TRUE(result) << result.message(); 733 | } 734 | 735 | TEST(Match, CheckRegexFail) { 736 | const auto result = Match("Heo", "CHECK: He{{[a-z]*}}o"); 737 | EXPECT_TRUE(result) << result.message(); 738 | } 739 | 740 | TEST(Match, MessageStringRegexRegexWithFalseStartFail) { 741 | const char* input = "He Hel Hell Hello Hello"; 742 | const char* checks = "CHECK: He{{ll}}oo"; 743 | const char* expected = R"(chklist:1:8: error: expected string not found in input 744 | CHECK: He{{ll}}oo 745 | ^ 746 | myin.txt:1:1: note: scanning from here 747 | He Hel Hell Hello Hello 748 | ^ 749 | )"; 750 | const auto result = 751 | Match(input, checks, 752 | Options().SetInputName("myin.txt").SetChecksName("chklist")); 753 | EXPECT_FALSE(result); 754 | EXPECT_THAT(result.message(), Eq(expected)) << result.message(); 755 | } 756 | 757 | TEST(Match, MessageStringRegexNotFoundWhenNeverMatchedAnything) { 758 | const char* input = R"(Begin 759 | Hello 760 | World)"; 761 | const char* checks = R"( 762 | Hello 763 | ; CHECK: He{{[0-9]+}}llo 764 | )"; 765 | const char* expected = R"(chklist:3:13: error: expected string not found in input 766 | ; CHECK: He{{[0-9]+}}llo 767 | ^ 768 | myin.txt:1:1: note: scanning from here 769 | Begin 770 | ^ 771 | )"; 772 | const auto result = 773 | Match(input, checks, 774 | Options().SetInputName("myin.txt").SetChecksName("chklist")); 775 | EXPECT_FALSE(result); 776 | EXPECT_THAT(result.message(), Eq(expected)) << result.message(); 777 | } 778 | 779 | 780 | // Statefulness: variable definitions and uses 781 | 782 | TEST(Match, VarDefFollowedByUsePass) { 783 | const auto result = 784 | Match("Hello\nHello", "CHECK: H[[X:[a-z]+]]o\nCHECK-NEXT: H[[X]]o"); 785 | EXPECT_TRUE(result) << result.message(); 786 | } 787 | 788 | TEST(Match, VarDefFollowedByUseFail) { 789 | const auto result = 790 | Match("Hello\n\nWorld", "CHECK: H[[X:[a-z]+]]o\nCHECK: H[[X]]o"); 791 | EXPECT_FALSE(result) << result.message(); 792 | EXPECT_THAT(result.message(), 793 | HasSubstr(":2:8: error: expected string not found in input")); 794 | EXPECT_THAT(result.message(), 795 | HasSubstr("note: with variable \"X\" equal to \"ell\"")); 796 | } 797 | 798 | TEST(Match, VarDefFollowedByUseFailAfterDAG) { 799 | const auto result = 800 | Match("Hello\nWorld", 801 | "CHECK: H[[X:[a-z]+]]o\nCHECK-DAG: box[[X]]\nCHECK: H[[X]]o"); 802 | EXPECT_FALSE(result) << result.message(); 803 | EXPECT_THAT(result.message(), 804 | HasSubstr(":2:12: error: expected string not found in input")); 805 | EXPECT_THAT(result.message(), 806 | HasSubstr("note: with variable \"X\" equal to \"ell\"")); 807 | } 808 | 809 | TEST(Match, VarDefFollowedByUseInNotCheck) { 810 | const auto result = 811 | Match("Hello\nHello", "CHECK: H[[X:[a-z]+]]o\nCHECK-NOT: H[[X]]o"); 812 | EXPECT_FALSE(result) << result.message(); 813 | EXPECT_THAT(result.message(), HasSubstr("CHECK-NOT: string occurred")); 814 | EXPECT_THAT(result.message(), 815 | HasSubstr("note: with variable \"X\" equal to \"ell\"")); 816 | } 817 | 818 | TEST(Match, VarDefFollowedByUseInNextCheckRightLine) { 819 | const auto result = 820 | Match("Hello\nHello", "CHECK: H[[X:[a-z]+]]o\nCHECK-NEXT: Blad[[X]]"); 821 | EXPECT_FALSE(result) << result.message(); 822 | EXPECT_THAT(result.message(), 823 | HasSubstr(":2:13: error: expected string not found in input")); 824 | EXPECT_THAT(result.message(), 825 | HasSubstr("note: with variable \"X\" equal to \"ell\"")); 826 | } 827 | 828 | TEST(Match, VarDefFollowedByUseInNextCheckBadLine) { 829 | const auto result = 830 | Match("Hello\n\nHello", "CHECK: H[[X:[a-z]+]]o\nCHECK-NEXT: H[[X]]o"); 831 | EXPECT_FALSE(result) << result.message(); 832 | EXPECT_THAT(result.message(), 833 | HasSubstr(":2:13: error: CHECK-NEXT: is not on the line after")); 834 | EXPECT_THAT(result.message(), 835 | HasSubstr("note: with variable \"X\" equal to \"ell\"")); 836 | } 837 | 838 | TEST(Match, UndefinedVarNeverMatches) { 839 | const auto result = Match("Hello HeXllo", "CHECK: He[[X]]llo"); 840 | EXPECT_FALSE(result) << result.message(); 841 | EXPECT_THAT(result.message(), 842 | HasSubstr("note: uses undefined variable \"X\"")); 843 | } 844 | 845 | TEST(Match, NoteSeveralUndefinedVariables) { 846 | const auto result = Match("Hello HeXllo", "CHECK: He[[X]]l[[YZ]]lo[[Q]]"); 847 | EXPECT_FALSE(result) << result.message(); 848 | const char* substr = R"( 849 | :1:1: note: uses undefined variable "X" 850 | Hello HeXllo 851 | ^ 852 | :1:1: note: uses undefined variable "YZ" 853 | Hello HeXllo 854 | ^ 855 | :1:1: note: uses undefined variable "Q" 856 | Hello HeXllo 857 | ^ 858 | )"; 859 | EXPECT_THAT(result.message(), HasSubstr(substr)); 860 | } 861 | 862 | TEST(Match, OutOfOrderDefAndUseViaDAGChecks) { 863 | // In this example the X variable should be set to 'l', and then match 864 | // the earlier occurrence in 'Hello'. 865 | const auto result = Match( 866 | "Hello\nWorld", "CHECK-DAG: Wor[[X:[a-z]+]]d\nCHECK-DAG: He[[X]]lo"); 867 | EXPECT_FALSE(result) << result.message(); 868 | } 869 | 870 | TEST(Match, VarDefRegexCountsParenthesesProperlyPass) { 871 | const auto result = Match( 872 | "FirstabababSecondcdcd\n1ababab2cdcd", 873 | "CHECK: First[[X:(ab)+]]Second[[Y:(cd)+]]\nCHECK: 1[[X]]2[[Y]]"); 874 | EXPECT_TRUE(result) << result.message(); 875 | } 876 | 877 | TEST(Match, VarDefRegexCountsParenthesesProperlyFail) { 878 | const auto result = 879 | Match("Firstababab1abab", "CHECK: First[[X:(ab)+]]\nCHECK: 1[[X]]"); 880 | EXPECT_FALSE(result) << result.message(); 881 | const char* substr = R"(:2:8: error: expected string not found in input 882 | CHECK: 1[[X]] 883 | ^ 884 | :1:12: note: scanning from here 885 | Firstababab1abab 886 | ^ 887 | :1:12: note: with variable "X" equal to "ababab" 888 | Firstababab1abab 889 | ^ 890 | )"; 891 | EXPECT_THAT(result.message(), HasSubstr(substr)); 892 | } 893 | 894 | } // namespace 895 | -------------------------------------------------------------------------------- /effcee/options_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Effcee Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "gmock/gmock.h" 16 | 17 | #include "effcee.h" 18 | 19 | namespace { 20 | 21 | using effcee::Options; 22 | using ::testing::Eq; 23 | using ::testing::Not; 24 | 25 | // Options class 26 | 27 | // Prefix property 28 | 29 | TEST(Options, DefaultPrefixIsCHECK) { 30 | EXPECT_THAT(Options().prefix(), "CHECK"); 31 | } 32 | 33 | TEST(Options, SetPrefixReturnsSelf) { 34 | Options options; 35 | const Options& other = options.SetPrefix(""); 36 | EXPECT_THAT(&other, &options); 37 | } 38 | 39 | TEST(Options, SetPrefixOnceSetsPrefix) { 40 | Options options; 41 | options.SetPrefix("foo"); 42 | EXPECT_THAT(options.prefix(), Eq("foo")); 43 | } 44 | 45 | TEST(Options, SetPrefixCopiesString) { 46 | Options options; 47 | std::string original("foo"); 48 | options.SetPrefix(original); 49 | EXPECT_THAT(options.prefix().data(), Not(Eq(original.data()))); 50 | } 51 | 52 | TEST(Options, SetPrefixEmptyStringPossible) { 53 | Options options; 54 | // This is not useful. 55 | options.SetPrefix(""); 56 | EXPECT_THAT(options.prefix(), Eq("")); 57 | } 58 | 59 | TEST(Options, SetPrefixTwiceRetainsLastPrefix) { 60 | Options options; 61 | options.SetPrefix("foo"); 62 | options.SetPrefix("bar baz"); 63 | EXPECT_THAT(options.prefix(), Eq("bar baz")); 64 | } 65 | 66 | 67 | // Input name property 68 | 69 | TEST(Options, DefaultInputNameIsStdin) { 70 | EXPECT_THAT(Options().input_name(), ""); 71 | } 72 | 73 | TEST(Options, SetInputNameReturnsSelf) { 74 | Options options; 75 | const Options& other = options.SetInputName(""); 76 | EXPECT_THAT(&other, &options); 77 | } 78 | 79 | TEST(Options, SetInputNameOnceSetsInputName) { 80 | Options options; 81 | options.SetInputName("foo"); 82 | EXPECT_THAT(options.input_name(), Eq("foo")); 83 | } 84 | 85 | TEST(Options, SetInputNameCopiesString) { 86 | Options options; 87 | std::string original("foo"); 88 | options.SetInputName(original); 89 | EXPECT_THAT(options.input_name().data(), Not(Eq(original.data()))); 90 | } 91 | 92 | TEST(Options, SetInputNameEmptyStringPossible) { 93 | Options options; 94 | options.SetInputName(""); 95 | EXPECT_THAT(options.input_name(), Eq("")); 96 | } 97 | 98 | TEST(Options, SetInputNameTwiceRetainsLastInputName) { 99 | Options options; 100 | options.SetInputName("foo"); 101 | options.SetInputName("bar baz"); 102 | EXPECT_THAT(options.input_name(), Eq("bar baz")); 103 | } 104 | 105 | // Checks name property 106 | 107 | TEST(Options, DefaultChecksNameIsStdin) { 108 | EXPECT_THAT(Options().checks_name(), ""); 109 | } 110 | 111 | TEST(Options, SetChecksNameReturnsSelf) { 112 | Options options; 113 | const Options& other = options.SetChecksName(""); 114 | EXPECT_THAT(&other, &options); 115 | } 116 | 117 | TEST(Options, SetChecksNameOnceSetsChecksName) { 118 | Options options; 119 | options.SetChecksName("foo"); 120 | EXPECT_THAT(options.checks_name(), Eq("foo")); 121 | } 122 | 123 | TEST(Options, SetChecksNameCopiesString) { 124 | Options options; 125 | std::string original("foo"); 126 | options.SetChecksName(original); 127 | EXPECT_THAT(options.checks_name().data(), Not(Eq(original.data()))); 128 | } 129 | 130 | TEST(Options, SetChecksNameEmptyStringPossible) { 131 | Options options; 132 | options.SetChecksName(""); 133 | EXPECT_THAT(options.checks_name(), Eq("")); 134 | } 135 | 136 | TEST(Options, SetChecksNameTwiceRetainsLastChecksName) { 137 | Options options; 138 | options.SetChecksName("foo"); 139 | options.SetChecksName("bar baz"); 140 | EXPECT_THAT(options.checks_name(), Eq("bar baz")); 141 | } 142 | 143 | } // namespace 144 | -------------------------------------------------------------------------------- /effcee/result_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Effcee Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include "gmock/gmock.h" 17 | 18 | #include "effcee.h" 19 | 20 | namespace { 21 | 22 | using effcee::Result; 23 | using ::testing::Combine; 24 | using ::testing::Eq; 25 | using ::testing::Not; 26 | using ::testing::ValuesIn; 27 | 28 | using Status = effcee::Result::Status; 29 | 30 | // Result class 31 | 32 | // Returns a vector of all failure status values. 33 | std::vector AllFailStatusValues() { 34 | return {Status::NoRules, Status::BadRule}; 35 | } 36 | 37 | // Returns a vector of all status values. 38 | std::vector AllStatusValues() { 39 | auto result = AllFailStatusValues(); 40 | result.push_back(Status::Ok); 41 | return result; 42 | } 43 | 44 | // Test one-argument constructor. 45 | 46 | using ResultStatusTest = ::testing::TestWithParam; 47 | 48 | TEST_P(ResultStatusTest, ConstructWithAnyStatus) { 49 | Result result(GetParam()); 50 | EXPECT_THAT(result.status(), Eq(GetParam())); 51 | } 52 | 53 | INSTANTIATE_TEST_SUITE_P(AllStatus, ResultStatusTest, 54 | ValuesIn(AllStatusValues())); 55 | 56 | // Test two-argument constructor. 57 | 58 | using ResultStatusMessageCase = std::tuple; 59 | 60 | using ResultStatusMessageTest = 61 | ::testing::TestWithParam; 62 | 63 | TEST_P(ResultStatusMessageTest, ConstructWithStatusAndMessage) { 64 | Result result(std::get<0>(GetParam()), std::get<1>(GetParam())); 65 | EXPECT_THAT(result.status(), Eq(std::get<0>(GetParam()))); 66 | EXPECT_THAT(result.message(), Eq(std::get<1>(GetParam()))); 67 | } 68 | 69 | INSTANTIATE_TEST_SUITE_P(SampleStatusAndMessage, ResultStatusMessageTest, 70 | Combine(ValuesIn(AllStatusValues()), 71 | ValuesIn(std::vector{ 72 | "", "foo bar", "and, how!\n"}))); 73 | 74 | TEST(ResultConversionTest, OkStatusConvertsToTrue) { 75 | Result result(Status::Ok); 76 | bool as_bool = result; 77 | EXPECT_THAT(as_bool, Eq(true)); 78 | } 79 | 80 | // Test conversion to bool. 81 | 82 | using ResultFailConversionTest = ::testing::TestWithParam; 83 | 84 | TEST_P(ResultFailConversionTest, AnyFailStatusConvertsToFalse) { 85 | Result result(GetParam()); 86 | bool as_bool = result; 87 | EXPECT_THAT(as_bool, Eq(false)); 88 | } 89 | 90 | INSTANTIATE_TEST_SUITE_P(FailStatus, ResultFailConversionTest, 91 | ValuesIn(AllFailStatusValues())); 92 | 93 | TEST(ResultMessage, SetMessageReturnsSelf) { 94 | Result result(Status::Ok); 95 | Result& other = result.SetMessage(""); 96 | EXPECT_THAT(&other, Eq(&result)); 97 | } 98 | 99 | TEST(ResultMessage, MessageDefaultsToEmpty) { 100 | Result result(Status::Ok); 101 | EXPECT_THAT(result.message(), Eq("")); 102 | } 103 | 104 | TEST(ResultMessage, SetMessageOnceSetsMessage) { 105 | Result result(Status::Ok); 106 | result.SetMessage("foo"); 107 | EXPECT_THAT(result.message(), Eq("foo")); 108 | } 109 | 110 | TEST(ResultMessage, SetMessageCopiesString) { 111 | Result result(Status::Ok); 112 | std::string original("foo"); 113 | result.SetMessage(original); 114 | EXPECT_THAT(result.message().data(), Not(Eq(original.data()))); 115 | } 116 | 117 | TEST(ResultMessage, SetMessageEmtpyStringPossible) { 118 | Result result(Status::Ok); 119 | result.SetMessage(""); 120 | EXPECT_THAT(result.message(), Eq("")); 121 | } 122 | 123 | TEST(ResultMessage, SetMessageTwiceRetainsLastMessage) { 124 | Result result(Status::Ok); 125 | result.SetMessage("foo"); 126 | result.SetMessage("bar baz"); 127 | EXPECT_THAT(result.message(), Eq("bar baz")); 128 | } 129 | 130 | } // namespace 131 | -------------------------------------------------------------------------------- /effcee/to_string.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Effcee Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef EFFCEE_TO_STRING_H 16 | #define EFFCEE_TO_STRING_H 17 | 18 | #include 19 | #include "effcee.h" 20 | 21 | namespace effcee { 22 | 23 | // Returns a copy of a StringPiece, as a std::string. 24 | inline std::string ToString(effcee::StringPiece s) { 25 | return std::string(s.data(), s.size()); 26 | } 27 | } // namespace effcee 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(effcee-example main.cc) 2 | 3 | target_link_libraries(effcee-example effcee) 4 | if(UNIX AND NOT MINGW) 5 | set_target_properties(effcee-example PROPERTIES LINK_FLAGS -pthread) 6 | endif() 7 | if (WIN32 AND NOT MSVC) 8 | # For MinGW cross-compile, statically link to the C++ runtime 9 | set_target_properties(effcee-example PROPERTIES 10 | LINK_FLAGS "-static -static-libgcc -static-libstdc++") 11 | endif(WIN32 AND NOT MSVC) 12 | 13 | 14 | if(EFFCEE_BUILD_TESTING) 15 | add_test(NAME effcee-example 16 | COMMAND Python3::Interpreter 17 | effcee-example-driver.py 18 | $ 19 | example_data.txt 20 | "CHECK: Hello" 21 | "CHECK-SAME: world" 22 | "CHECK-NEXT: Bees" 23 | "CHECK-NOT: Sting" 24 | "CHECK: Honey" 25 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) 26 | endif(EFFCEE_BUILD_TESTING) 27 | -------------------------------------------------------------------------------- /examples/effcee-example-driver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright 2017 The Effcee Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | """Execute the effcee-example program, where the arguments are 18 | the location of the example program, the input file, and a 19 | list of check rules to match against the input. 20 | 21 | Args: 22 | effcee-example: Path to the effcee-example executable 23 | input_file: Data file containing the input to match 24 | check1 .. checkN: Check rules to match 25 | """ 26 | 27 | import subprocess 28 | import sys 29 | 30 | def main(): 31 | cmd = sys.argv[1] 32 | input_file = sys.argv[2] 33 | checks = sys.argv[3:] 34 | args = [cmd] 35 | args.extend(checks) 36 | print(args) 37 | with open(input_file) as input_stream: 38 | sys.exit(subprocess.call(args, stdin=input_stream)) 39 | sys.exit(1) 40 | 41 | if __name__ == '__main__': 42 | main() 43 | -------------------------------------------------------------------------------- /examples/example_data.txt: -------------------------------------------------------------------------------- 1 | Hello world 2 | Bees 3 | Make 4 | Delicious Honey 5 | -------------------------------------------------------------------------------- /examples/main.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Effcee Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | 18 | #include "effcee/effcee.h" 19 | 20 | // Checks standard input against the list of checks provided as command line 21 | // arguments. 22 | // 23 | // Example: 24 | // cat <sample_data.txt 25 | // Bees 26 | // Make 27 | // Delicious Honey 28 | // EOF 29 | // effcee-example > input_stream.rdbuf(); 39 | 40 | // Attempt to match. The input and checks arguments can be provided as 41 | // std::string or pointer to char. 42 | auto result = effcee::Match(input_stream.str(), checks_stream.str(), 43 | effcee::Options().SetChecksName("checks")); 44 | 45 | // Successful match result converts to true. 46 | if (result) { 47 | std::cout << "The input matched your check list!" << std::endl; 48 | } else { 49 | // Otherwise, you can get a status code and a detailed message. 50 | switch (result.status()) { 51 | case effcee::Result::Status::NoRules: 52 | std::cout << "error: Expected check rules as command line arguments\n"; 53 | break; 54 | case effcee::Result::Status::Fail: 55 | std::cout << "The input failed to match your check rules:\n"; 56 | break; 57 | default: 58 | break; 59 | } 60 | std::cout << result.message() << std::endl; 61 | return 1; 62 | } 63 | return 0; 64 | } 65 | -------------------------------------------------------------------------------- /fuzzer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (EXISTS "${EFFCEE_FUZZED_DATA_PROVIDER_DIR}/FuzzedDataProvider.h") 2 | message(STATUS "effcee: configuring effcee-fuzz") 3 | add_executable(effcee-fuzz effcee_fuzz.cc) 4 | effcee_default_compile_options(effcee-fuzz) 5 | target_include_directories(effcee-fuzz PRIVATE "${EFFCEE_FUZZED_DATA_PROVIDER_DIR}") 6 | target_link_libraries(effcee-fuzz PRIVATE effcee) 7 | 8 | if(UNIX AND NOT MINGW) 9 | set_target_properties(effcee-fuzz PROPERTIES LINK_FLAGS -pthread) 10 | endif() 11 | if (WIN32 AND NOT MSVC) 12 | # For MinGW cross-compile, statically link to the C++ runtime 13 | set_target_properties(effcee-fuzz PROPERTIES 14 | LINK_FLAGS "-static -static-libgcc -static-libstdc++") 15 | endif(WIN32 AND NOT MSVC) 16 | else() 17 | message(STATUS "effcee: effcee-fuzz won't be built. Can't find FuzzedDataProvider.h") 18 | endif() 19 | -------------------------------------------------------------------------------- /fuzzer/effcee_fuzz.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Effcee Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | 18 | #include "FuzzedDataProvider.h" 19 | #include "effcee/effcee.h" 20 | 21 | // Consumes standard input as a fuzzer input, breaks it apart into text 22 | // and a check, then runs a basic match. 23 | int main(int argc, char* argv[]) { 24 | std::vector input; 25 | // Read standard input into a buffer. 26 | { 27 | if (FILE* fp = freopen(nullptr, "rb", stdin)) { 28 | uint8_t chunk[1024]; 29 | while (size_t len = fread(chunk, sizeof(uint8_t), sizeof(chunk), fp)) { 30 | input.insert(input.end(), chunk, chunk + len); 31 | } 32 | if (ftell(fp) == -1L) { 33 | if (ferror(fp)) { 34 | fprintf(stderr, "error: error reading standard input"); 35 | } 36 | return 1; 37 | } 38 | } else { 39 | fprintf(stderr, "error: couldn't reopen stdin for binary reading"); 40 | } 41 | } 42 | 43 | // This is very basic, but can find bugs. 44 | FuzzedDataProvider stream(input.data(), input.size()); 45 | std::string text = stream.ConsumeRandomLengthString(input.size()); 46 | std::string checks = stream.ConsumeRemainingBytesAsString(); 47 | effcee::Match(text, checks); 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /kokoro/check-format/build-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2025 The Effcee Authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # Fail on any error. 17 | set -e 18 | 19 | # This is required to run any git command in the docker since owner will 20 | # have changed between the clone environment, and the docker container. 21 | # Marking the root of the repo as safe for ownership changes. 22 | git config --global --add safe.directory "$PWD" 23 | 24 | echo $(date): Check formatting... 25 | ./utils/check_code_format.sh ${1:-main} 26 | echo $(date): check completed. 27 | -------------------------------------------------------------------------------- /kokoro/check-format/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2025 The Effcee Authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # Fail on any error. 17 | set -e 18 | 19 | SCRIPT_DIR="$( cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" 20 | SRC_ROOT="$( cd "${SCRIPT_DIR}/../.." >/dev/null 2>&1 && pwd )" 21 | TARGET_BRANCH="${KOKORO_GITHUB_PULL_REQUEST_TARGET_BRANCH-main}" 22 | 23 | docker run --rm -i \ 24 | --volume "${SRC_ROOT}:${SRC_ROOT}" \ 25 | --workdir "${SRC_ROOT}" \ 26 | "us-east4-docker.pkg.dev/shaderc-build/radial-docker/ubuntu-24.04-amd64/formatter" \ 27 | "${SCRIPT_DIR}/build-docker.sh" "${TARGET_BRANCH}" 28 | -------------------------------------------------------------------------------- /kokoro/check-format/presubmit.cfg: -------------------------------------------------------------------------------- 1 | # Copyright 2025 The Effcee Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Presubmit build configuration. 16 | build_file: "effcee/kokoro/check-format/build.sh" 17 | -------------------------------------------------------------------------------- /kokoro/linux-clang-debug/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2023 Google LLC. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # Linux Build Script. 17 | 18 | # Fail on any error. 19 | set -e 20 | # Display commands being run. 21 | set -x 22 | 23 | SCRIPT_DIR=`dirname "$BASH_SOURCE"` 24 | source $SCRIPT_DIR/../scripts/linux/build.sh DEBUG clang cmake 25 | -------------------------------------------------------------------------------- /kokoro/linux-clang-debug/continuous.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Continuous build configuration. 16 | build_file: "effcee/kokoro/linux-clang-debug/build.sh" 17 | -------------------------------------------------------------------------------- /kokoro/linux-clang-debug/presubmit.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Presubmit build configuration. 16 | build_file: "effcee/kokoro/linux-clang-debug/build.sh" 17 | -------------------------------------------------------------------------------- /kokoro/linux-clang-release-bazel/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2023 Google LLC. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # Linux Build Script. 17 | 18 | # Fail on any error. 19 | set -e 20 | # Display commands being run. 21 | set -x 22 | 23 | SCRIPT_DIR=`dirname "$BASH_SOURCE"` 24 | source $SCRIPT_DIR/../scripts/linux/build.sh RELEASE clang bazel 25 | -------------------------------------------------------------------------------- /kokoro/linux-clang-release-bazel/continuous.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Continuous build configuration. 16 | build_file: "effcee/kokoro/linux-clang-release-bazel/build.sh" 17 | -------------------------------------------------------------------------------- /kokoro/linux-clang-release-bazel/presubmit.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Presubmit build configuration. 16 | build_file: "effcee/kokoro/linux-clang-release-bazel/build.sh" 17 | -------------------------------------------------------------------------------- /kokoro/linux-clang-release/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2023 Google LLC. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # Linux Build Script. 17 | 18 | # Fail on any error. 19 | set -e 20 | # Display commands being run. 21 | set -x 22 | 23 | SCRIPT_DIR=`dirname "$BASH_SOURCE"` 24 | source $SCRIPT_DIR/../scripts/linux/build.sh RELEASE clang cmake 25 | -------------------------------------------------------------------------------- /kokoro/linux-clang-release/continuous.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Continuous build configuration. 16 | build_file: "effcee/kokoro/linux-clang-release/build.sh" 17 | -------------------------------------------------------------------------------- /kokoro/linux-clang-release/presubmit.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Presubmit build configuration. 16 | build_file: "effcee/kokoro/linux-clang-release/build.sh" 17 | -------------------------------------------------------------------------------- /kokoro/linux-gcc-debug/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2023 Google LLC. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # Linux Build Script. 17 | 18 | # Fail on any error. 19 | set -e 20 | # Display commands being run. 21 | set -x 22 | 23 | SCRIPT_DIR=`dirname "$BASH_SOURCE"` 24 | source $SCRIPT_DIR/../scripts/linux/build.sh DEBUG gcc cmake 25 | -------------------------------------------------------------------------------- /kokoro/linux-gcc-debug/continuous.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Continuous build configuration. 16 | build_file: "effcee/kokoro/linux-gcc-debug/build.sh" 17 | -------------------------------------------------------------------------------- /kokoro/linux-gcc-debug/presubmit.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Presubmit build configuration. 16 | build_file: "effcee/kokoro/linux-gcc-debug/build.sh" 17 | 18 | -------------------------------------------------------------------------------- /kokoro/linux-gcc-release/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2023 Google LLC. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # Linux Build Script. 17 | 18 | # Fail on any error. 19 | set -e 20 | # Display commands being run. 21 | set -x 22 | 23 | SCRIPT_DIR=`dirname "$BASH_SOURCE"` 24 | source $SCRIPT_DIR/../scripts/linux/build.sh RELEASE gcc cmake 25 | -------------------------------------------------------------------------------- /kokoro/linux-gcc-release/continuous.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Continuous build configuration. 16 | build_file: "effcee/kokoro/linux-gcc-release/build.sh" 17 | -------------------------------------------------------------------------------- /kokoro/linux-gcc-release/presubmit.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Presubmit build configuration. 16 | build_file: "effcee/kokoro/linux-gcc-release/build.sh" 17 | -------------------------------------------------------------------------------- /kokoro/macos-clang-debug/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2023 Google LLC. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # MacOS Build Script. 17 | 18 | # Fail on any error. 19 | set -e 20 | # Display commands being run. 21 | set -x 22 | 23 | SCRIPT_DIR=`dirname "$BASH_SOURCE"` 24 | source $SCRIPT_DIR/../scripts/macos/build.sh Debug 25 | -------------------------------------------------------------------------------- /kokoro/macos-clang-debug/continuous.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Continuous build configuration. 16 | build_file: "effcee/kokoro/macos-clang-debug/build.sh" 17 | -------------------------------------------------------------------------------- /kokoro/macos-clang-debug/presubmit.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Presubmit build configuration. 16 | build_file: "effcee/kokoro/macos-clang-debug/build.sh" 17 | -------------------------------------------------------------------------------- /kokoro/macos-clang-release-bazel/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2023 Google LLC. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # Linux Build Script. 17 | 18 | # Fail on any error. 19 | set -e 20 | # Display commands being run. 21 | set -x 22 | 23 | CC=clang 24 | CXX=clang++ 25 | SRC=$PWD/github/effcee 26 | 27 | # This is required to run any git command in the docker since owner will 28 | # have changed between the clone environment, and the docker container. 29 | # Marking the root of the repo as safe for ownership changes. 30 | git config --global --add safe.directory $SRC 31 | 32 | cd $SRC 33 | /usr/bin/python3 utils/git-sync-deps 34 | 35 | # Get bazel 7.0.2 36 | gsutil cp gs://bazel/7.0.2/release/bazel-7.0.2-darwin-x86_64 . 37 | chmod +x bazel-7.0.2-darwin-x86_64 38 | 39 | echo $(date): Build everything... 40 | ./bazel-7.0.2-darwin-x86_64 build --cxxopt=-std=c++17 :all 41 | echo $(date): Build completed. 42 | 43 | echo $(date): Starting bazel test... 44 | ./bazel-7.0.2-darwin-x86_64 test --cxxopt=-std=c++17 :all 45 | echo $(date): Bazel test completed. 46 | -------------------------------------------------------------------------------- /kokoro/macos-clang-release-bazel/continuous.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Continuous build configuration. 16 | build_file: "effcee/kokoro/macos-clang-release-bazel/build.sh" 17 | -------------------------------------------------------------------------------- /kokoro/macos-clang-release-bazel/presubmit.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Presubmit build configuration. 16 | build_file: "effcee/kokoro/macos-clang-release-bazel/build.sh" 17 | -------------------------------------------------------------------------------- /kokoro/macos-clang-release/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2023 Google LLC. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # MacOS Build Script. 17 | 18 | # Fail on any error. 19 | set -e 20 | # Display commands being run. 21 | set -x 22 | 23 | SCRIPT_DIR=`dirname "$BASH_SOURCE"` 24 | source $SCRIPT_DIR/../scripts/macos/build.sh RelWithDebInfo 25 | -------------------------------------------------------------------------------- /kokoro/macos-clang-release/continuous.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Continuous build configuration. 16 | build_file: "effcee/kokoro/macos-clang-release/build.sh" 17 | -------------------------------------------------------------------------------- /kokoro/macos-clang-release/presubmit.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Presubmit build configuration. 16 | build_file: "effcee/kokoro/macos-clang-release/build.sh" 17 | -------------------------------------------------------------------------------- /kokoro/scripts/linux/build-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2023 Google LLC. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # Linux Build Script. 17 | 18 | # Fail on any error. 19 | set -e 20 | # Display commands being run. 21 | set -x 22 | 23 | function die { 24 | echo "$@" 25 | exit 1 26 | } 27 | 28 | 29 | # This is required to run any git command in the docker since owner will 30 | # have changed between the clone environment, and the docker container. 31 | # Marking the root of the repo as safe for ownership changes. 32 | git config --global --add safe.directory $ROOT_DIR 33 | 34 | . /bin/using.sh # Declare the bash `using` function for configuring toolchains. 35 | 36 | using python-3.12 37 | 38 | case $CONFIG in 39 | DEBUG|RELEASE) 40 | ;; 41 | *) 42 | die expected CONFIG to be DEBUG or RELEASE 43 | ;; 44 | esac 45 | 46 | case $COMPILER in 47 | clang) 48 | using clang-13.0.1 49 | ;; 50 | gcc) 51 | using gcc-13 52 | ;; 53 | *) 54 | die expected COMPILER to be clang or gcc 55 | ;; 56 | esac 57 | 58 | case $TOOL in 59 | cmake|bazel) 60 | ;; 61 | *) 62 | die expected TOOL to be cmake or bazel 63 | ;; 64 | esac 65 | 66 | cd $ROOT_DIR 67 | 68 | function clean_dir() { 69 | dir=$1 70 | if [[ -d "$dir" ]]; then 71 | rm -fr "$dir" 72 | fi 73 | mkdir "$dir" 74 | } 75 | 76 | # Get source for dependencies, as specified in the DEPS file 77 | /usr/bin/python3 utils/git-sync-deps 78 | 79 | if [ $TOOL = "cmake" ]; then 80 | using cmake-3.31.2 81 | using ninja-1.10.0 82 | 83 | BUILD_TYPE="Debug" 84 | if [ $CONFIG = "RELEASE" ]; then 85 | BUILD_TYPE="RelWithDebInfo" 86 | fi 87 | 88 | clean_dir "$ROOT_DIR/build" 89 | cd "$ROOT_DIR/build" 90 | 91 | # Invoke the build. 92 | BUILD_SHA=${KOKORO_GITHUB_COMMIT:-$KOKORO_GITHUB_PULL_REQUEST_COMMIT} 93 | echo $(date): Starting build... 94 | cmake -DPYTHON_EXECUTABLE:FILEPATH=/usr/bin/python3 -GNinja -DCMAKE_INSTALL_PREFIX=$KOKORO_ARTIFACTS_DIR/install -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DRE2_BUILD_TESTING=OFF .. 95 | 96 | echo $(date): Build everything... 97 | ninja 98 | echo $(date): Build completed. 99 | 100 | echo $(date): Starting ctest... 101 | ctest --output-on-failure --timeout 300 102 | echo $(date): ctest completed. 103 | elif [ $TOOL = "bazel" ]; then 104 | using bazel-7.0.2 105 | 106 | echo $(date): Build everything... 107 | bazel build --cxxopt=-std=c++17 :all 108 | echo $(date): Build completed. 109 | 110 | echo $(date): Starting bazel test... 111 | bazel test --cxxopt=-std=c++17 :all 112 | echo $(date): Bazel test completed. 113 | fi 114 | -------------------------------------------------------------------------------- /kokoro/scripts/linux/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2023 Google LLC. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # Linux Build Script. 17 | 18 | # Fail on any error. 19 | set -e 20 | 21 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" 22 | ROOT_DIR="$( cd "${SCRIPT_DIR}/../../.." >/dev/null 2>&1 && pwd )" 23 | 24 | CONFIG=$1 # DEBUG RELEASE 25 | COMPILER=$2 # clang gcc 26 | TOOL=$3 # cmake bazel 27 | BUILD_SHA=${KOKORO_GITHUB_COMMIT:-$KOKORO_GITHUB_PULL_REQUEST_COMMIT} 28 | 29 | # chown the given directory to the current user, if it exists. 30 | # Docker creates files with the root user - this can upset the Kokoro artifact copier. 31 | function chown_dir() { 32 | dir=$1 33 | if [[ -d "$dir" ]]; then 34 | sudo chown -R "$(id -u):$(id -g)" "$dir" 35 | fi 36 | } 37 | 38 | set +e 39 | # Allow build failures 40 | 41 | # "--privileged" is required to run ptrace in the asan builds. 42 | docker run --rm -i \ 43 | --privileged \ 44 | --volume "${ROOT_DIR}:${ROOT_DIR}" \ 45 | --volume "${KOKORO_ARTIFACTS_DIR}:${KOKORO_ARTIFACTS_DIR}" \ 46 | --workdir "${ROOT_DIR}" \ 47 | --env SCRIPT_DIR=${SCRIPT_DIR} \ 48 | --env ROOT_DIR=${ROOT_DIR} \ 49 | --env CONFIG=${CONFIG} \ 50 | --env COMPILER=${COMPILER} \ 51 | --env TOOL=${TOOL} \ 52 | --env KOKORO_ARTIFACTS_DIR="${KOKORO_ARTIFACTS_DIR}" \ 53 | --env BUILD_SHA="${BUILD_SHA}" \ 54 | --entrypoint "${SCRIPT_DIR}/build-docker.sh" \ 55 | us-east4-docker.pkg.dev/shaderc-build/radial-docker/ubuntu-24.04-amd64/cpp-builder 56 | RESULT=$? 57 | 58 | # This is important. If the permissions are not fixed, kokoro will fail 59 | # to pull build artifacts, and put the build in tool-failure state, which 60 | # blocks the logs. 61 | chown_dir "${ROOT_DIR}/build" 62 | chown_dir "${ROOT_DIR}/third_party" 63 | exit $RESULT 64 | -------------------------------------------------------------------------------- /kokoro/scripts/macos/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright (c) 2023 Google LLC. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # MacOS Build Script. 17 | 18 | # Fail on any error. 19 | set -e 20 | # Display commands being run. 21 | set -x 22 | 23 | BUILD_ROOT=$PWD 24 | SRC=$PWD/github/effcee 25 | BUILD_TYPE=$1 26 | 27 | case $BUILD_TYPE in 28 | Debug|RelWithDebInfo) 29 | ;; 30 | *) 31 | die expected build type to be Debug or RelWithDebInfo 32 | ;; 33 | esac 34 | 35 | # This is required to run any git command in the docker since owner will 36 | # have changed between the clone environment, and the docker container. 37 | # Marking the root of the repo as safe for ownership changes. 38 | git config --global --add safe.directory $SRC 39 | 40 | # Get NINJA. 41 | wget -q https://github.com/ninja-build/ninja/releases/download/v1.10.0/ninja-mac.zip 42 | unzip -q ninja-mac.zip 43 | chmod +x ninja 44 | export PATH="$PWD:$PATH" 45 | 46 | cd $SRC 47 | python3 utils/git-sync-deps 48 | 49 | mkdir build && cd $SRC/build 50 | 51 | # Invoke the build. 52 | BUILD_SHA=${KOKORO_GITHUB_COMMIT:-$KOKORO_GITHUB_PULL_REQUEST_COMMIT} 53 | echo $(date): Starting build... 54 | cmake \ 55 | -GNinja \ 56 | -DCMAKE_INSTALL_PREFIX=$KOKORO_ARTIFACTS_DIR/install \ 57 | -DCMAKE_C_COMPILER=clang \ 58 | -DCMAKE_CXX_COMPILER=clang++ \ 59 | -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ 60 | -DRE_BUILD_TESTING=OFF \ 61 | .. 62 | 63 | echo $(date): Build everything... 64 | ninja 65 | echo $(date): Build completed. 66 | 67 | echo $(date): Starting ctest... 68 | ctest -j4 --output-on-failure --timeout 300 69 | echo $(date): ctest completed. 70 | 71 | # Package the build. 72 | ninja install 73 | -------------------------------------------------------------------------------- /kokoro/scripts/windows/build.bat: -------------------------------------------------------------------------------- 1 | :: Copyright (c) 2023 Google LLC. 2 | :: 3 | :: Licensed under the Apache License, Version 2.0 (the "License"); 4 | :: you may not use this file except in compliance with the License. 5 | :: You may obtain a copy of the License at 6 | :: 7 | :: http://www.apache.org/licenses/LICENSE-2.0 8 | :: 9 | :: Unless required by applicable law or agreed to in writing, software 10 | :: distributed under the License is distributed on an "AS IS" BASIS, 11 | :: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | :: See the License for the specific language governing permissions and 13 | :: limitations under the License. 14 | :: 15 | :: Windows Build Script. 16 | 17 | @echo on 18 | 19 | set BUILD_ROOT=%cd% 20 | set SRC=%cd%\github\effcee 21 | set BUILD_TYPE=%1 22 | set VS_VERSION=%2 23 | 24 | :: Force usage of python 3.12 25 | set PATH=C:\python312;%PATH% 26 | set PATH=C:\cmake-3.31.2\bin;%PATH% 27 | 28 | cmake --version 29 | 30 | :: ######################################### 31 | :: set up msvc build env 32 | :: ######################################### 33 | if %VS_VERSION% == 2019 ( 34 | call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 35 | echo "Using VS 2019..." 36 | ) else if %VS_VERSION% == 2022 ( 37 | call "C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvarsall.bat" x64 38 | echo "Using VS 2022..." 39 | ) 40 | 41 | cd %SRC% 42 | python utils/git-sync-deps 43 | 44 | mkdir build 45 | cd build 46 | 47 | :: ######################################### 48 | :: Start building. 49 | :: ######################################### 50 | echo "Starting build... %DATE% %TIME%" 51 | if "%KOKORO_GITHUB_COMMIT%." == "." ( 52 | set BUILD_SHA=%KOKORO_GITHUB_PULL_REQUEST_COMMIT% 53 | ) else ( 54 | set BUILD_SHA=%KOKORO_GITHUB_COMMIT% 55 | ) 56 | 57 | set CMAKE_FLAGS=-DCMAKE_INSTALL_PREFIX=%KOKORO_ARTIFACTS_DIR%\install -GNinja -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DRE2_BUILD_TESTING=OFF -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe 58 | 59 | cmake %CMAKE_FLAGS% .. 60 | 61 | if %ERRORLEVEL% NEQ 0 exit /b %ERRORLEVEL% 62 | 63 | echo "Build everything... %DATE% %TIME%" 64 | ninja 65 | if %ERRORLEVEL% NEQ 0 exit /b %ERRORLEVEL% 66 | echo "Build Completed %DATE% %TIME%" 67 | 68 | :: This lets us use !ERRORLEVEL! inside an IF ... () and get the actual error at that point. 69 | setlocal ENABLEDELAYEDEXPANSION 70 | 71 | :: ################################################ 72 | :: Run the tests 73 | :: ################################################ 74 | echo "Running Tests... %DATE% %TIME%" 75 | ctest -C %BUILD_TYPE% --output-on-failure --timeout 300 76 | if !ERRORLEVEL! NEQ 0 exit /b !ERRORLEVEL! 77 | echo "Tests Completed %DATE% %TIME%" 78 | 79 | :: ################################################ 80 | :: Install and package. 81 | :: ################################################ 82 | ninja install 83 | 84 | :: Clean up some directories. 85 | rm -rf %SRC%\build 86 | rm -rf %SRC%\external 87 | 88 | exit /b 0 89 | -------------------------------------------------------------------------------- /kokoro/windows-msvc-2019-release/build.bat: -------------------------------------------------------------------------------- 1 | :: Copyright (c) 2023 Google LLC 2 | :: 3 | :: Licensed under the Apache License, Version 2.0 (the "License"); 4 | :: you may not use this file except in compliance with the License. 5 | :: You may obtain a copy of the License at 6 | :: 7 | :: http://www.apache.org/licenses/LICENSE-2.0 8 | :: 9 | :: Unless required by applicable law or agreed to in writing, software 10 | :: distributed under the License is distributed on an "AS IS" BASIS, 11 | :: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | :: See the License for the specific language governing permissions and 13 | :: limitations under the License. 14 | :: 15 | :: Windows Build Script. 16 | 17 | @echo on 18 | 19 | :: Find out the directory of the common build script. 20 | set SCRIPT_DIR=%~dp0 21 | 22 | :: Call with correct parameter 23 | call %SCRIPT_DIR%\..\scripts\windows\build.bat RelWithDebInfo 2019 24 | 25 | -------------------------------------------------------------------------------- /kokoro/windows-msvc-2019-release/continuous.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Continuous build configuration. 16 | build_file: "effcee/kokoro/windows-msvc-2019-release/build.bat" 17 | -------------------------------------------------------------------------------- /kokoro/windows-msvc-2019-release/presubmit.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Presubmit build configuration. 16 | build_file: "effcee/kokoro/windows-msvc-2019-release/build.bat" 17 | -------------------------------------------------------------------------------- /kokoro/windows-msvc-2022-debug/build.bat: -------------------------------------------------------------------------------- 1 | :: Copyright (c) 2025 Google LLC 2 | :: 3 | :: Licensed under the Apache License, Version 2.0 (the "License"); 4 | :: you may not use this file except in compliance with the License. 5 | :: You may obtain a copy of the License at 6 | :: 7 | :: http://www.apache.org/licenses/LICENSE-2.0 8 | :: 9 | :: Unless required by applicable law or agreed to in writing, software 10 | :: distributed under the License is distributed on an "AS IS" BASIS, 11 | :: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | :: See the License for the specific language governing permissions and 13 | :: limitations under the License. 14 | :: 15 | :: Windows Build Script. 16 | 17 | @echo on 18 | 19 | :: Find out the directory of the common build script. 20 | set SCRIPT_DIR=%~dp0 21 | 22 | :: Call with correct parameter 23 | call %SCRIPT_DIR%\..\scripts\windows\build.bat Debug 2022 24 | -------------------------------------------------------------------------------- /kokoro/windows-msvc-2022-debug/continuous.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Continuous build configuration. 16 | build_file: "effcee/kokoro/windows-msvc-2022-debug/build.bat" 17 | -------------------------------------------------------------------------------- /kokoro/windows-msvc-2022-debug/presubmit.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Presubmit build configuration. 16 | build_file: "effcee/kokoro/windows-msvc-2022-debug/build.bat" 17 | -------------------------------------------------------------------------------- /kokoro/windows-msvc-2022-release/build.bat: -------------------------------------------------------------------------------- 1 | :: Copyright (c) 2025 Google LLC 2 | :: 3 | :: Licensed under the Apache License, Version 2.0 (the "License"); 4 | :: you may not use this file except in compliance with the License. 5 | :: You may obtain a copy of the License at 6 | :: 7 | :: http://www.apache.org/licenses/LICENSE-2.0 8 | :: 9 | :: Unless required by applicable law or agreed to in writing, software 10 | :: distributed under the License is distributed on an "AS IS" BASIS, 11 | :: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | :: See the License for the specific language governing permissions and 13 | :: limitations under the License. 14 | :: 15 | :: Windows Build Script. 16 | 17 | @echo on 18 | 19 | :: Find out the directory of the common build script. 20 | set SCRIPT_DIR=%~dp0 21 | 22 | :: Call with correct parameter 23 | call %SCRIPT_DIR%\..\scripts\windows\build.bat RelWithDebInfo 2022 24 | 25 | -------------------------------------------------------------------------------- /kokoro/windows-msvc-2022-release/continuous.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Continuous build configuration. 16 | build_file: "effcee/kokoro/windows-msvc-2022-release/build.bat" 17 | -------------------------------------------------------------------------------- /kokoro/windows-msvc-2022-release/presubmit.cfg: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Presubmit build configuration. 16 | build_file: "effcee/kokoro/windows-msvc-2022-release/build.bat" 17 | -------------------------------------------------------------------------------- /third_party/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Suppress all warnings from third-party projects. 2 | set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS -w) 3 | 4 | # Set alternate root directory for third party sources. 5 | set(EFFCEE_THIRD_PARTY_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}" CACHE STRING 6 | "Root location of all third_party projects") 7 | 8 | # Find googletest and gmock 9 | if(${googletest-distribution_SOURCE_DIR}) 10 | set(EFFCEE_GOOGLETEST_DIR "${googletest-distribution_SOURCE_DIR}" CACHE STRING 11 | "Location of googletest source") 12 | else() 13 | set(EFFCEE_GOOGLETEST_DIR "${EFFCEE_THIRD_PARTY_ROOT_DIR}/googletest" CACHE STRING 14 | "Location of googletest source") 15 | endif() 16 | 17 | # Find abseil 18 | if(absl_SOURCE_DIR) 19 | set(EFFCEE_ABSEIL_DIR "${absl_SOURCE_DIR}" CACHE STRING "Location of abseil source" FORCE) 20 | else() 21 | # Allow for abseil-cpp or abseil_cpp 22 | if (IS_DIRECTORY "${EFFCEE_THIRD_PARTY_ROOT_DIR}/abseil_cpp") 23 | set(EFFCEE_ABSEIL_DIR "${EFFCEE_THIRD_PARTY_ROOT_DIR}/abseil_cpp" CACHE STRING "Location of abseil source") 24 | elseif(IS_DIRECTORY "${EFFCEE_THIRD_PARTY_ROOT_DIR}/abseil-cpp") 25 | set(EFFCEE_ABSEIL_DIR "${EFFCEE_THIRD_PARTY_ROOT_DIR}/abseil-cpp" CACHE STRING "Location of abseil source") 26 | endif() 27 | endif() 28 | 29 | # Find re2 30 | if(RE2_SOURCE_DIR) 31 | set(EFFCEE_RE2_DIR "${RE2_SOURCE_DIR}" CACHE STRING "Location of re2 source" FORCE) 32 | else() 33 | set(EFFCEE_RE2_DIR "${EFFCEE_THIRD_PARTY_ROOT_DIR}/re2" CACHE STRING 34 | "Location of re2 source") 35 | endif() 36 | 37 | # Configure third party projects. 38 | if(EFFCEE_BUILD_TESTING) 39 | if (NOT TARGET gmock) 40 | if (IS_DIRECTORY ${EFFCEE_GOOGLETEST_DIR}) 41 | add_subdirectory(${EFFCEE_GOOGLETEST_DIR} googletest EXCLUDE_FROM_ALL) 42 | endif() 43 | endif() 44 | if (NOT TARGET gmock) 45 | message(FATAL_ERROR "gmock was not found - required for tests") 46 | endif() 47 | endif() 48 | 49 | if (NOT TARGET absl::base) 50 | if (IS_DIRECTORY ${EFFCEE_ABSEIL_DIR}) 51 | set(ABSL_INTERNAL_AT_LEAST_CXX17 ON) 52 | set(ABSL_PROPAGATE_CXX_STD ON) 53 | set(ABSL_ENABLE_INSTALL ON) 54 | add_subdirectory(${EFFCEE_ABSEIL_DIR} absl EXCLUDE_FROM_ALL) 55 | endif() 56 | endif() 57 | if (NOT TARGET absl::base) 58 | message(FATAL_ERROR "absl was not found - required for compilation") 59 | endif() 60 | 61 | if (NOT TARGET re2) 62 | if (IS_DIRECTORY ${EFFCEE_RE2_DIR}) 63 | add_subdirectory(${EFFCEE_RE2_DIR} re2 EXCLUDE_FROM_ALL) 64 | endif() 65 | endif() 66 | if (NOT TARGET re2) 67 | message(FATAL_ERROR "re2 was not found - required for compilation") 68 | endif() 69 | -------------------------------------------------------------------------------- /utils/check_code_format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2025 The Effcee Authors. 3 | 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # This script determines if the source code in a Pull Request is properly formatted. 17 | # Exits with non 0 exit code if formatting is needed. 18 | # Assumptions: 19 | # - git and python3 are on the path 20 | # - Runs from the project root diretory. 21 | # - 'clang-format' is on the path, or env var CLANG_FORMAT points to it. 22 | # - 'clang-format-diff.py' is in the utils directory, or env var 23 | # points to it.CLANG_FORMAT_DIFF 24 | 25 | BASE_BRANCH=$(git merge-base main HEAD) 26 | 27 | CLANG_FORMAT=${CLANG_FORMAT:-clang-format} 28 | if [ ! -f "$CLANG_FORMAT" ]; then 29 | echo missing clang-format: set CLANG_FORMAT or put clang-format in the PATH 30 | exit 1 31 | fi 32 | 33 | # Find clang-format-diff.py from an environment variable, or use a default 34 | CLANG_FORMAT_DIFF=${CLANG_FORMAT_DIFF:-./utils/clang-format-diff.py} 35 | if [ ! -f "$CLANG_FORMAT_DIFF" ]; then 36 | echo missing clang-format-diffy.py: set CLANG_FORMAT_DIFF or put it in ./utils/clang-format-diff.py 37 | exit 1 38 | fi 39 | 40 | echo "Comparing "$(git rev-parse HEAD)" against $BASE_BRANCH" 41 | 42 | FILES_TO_CHECK=$(git diff --name-only ${BASE_BRANCH} | grep -E ".*\.(cpp|cc|c\+\+|cxx|c|h|hpp)$") 43 | 44 | if [ -z "${FILES_TO_CHECK}" ]; then 45 | echo "No source code to check for formatting." 46 | exit 0 47 | fi 48 | 49 | FORMAT_DIFF=$(git diff -U0 ${BASE_BRANCH} -- ${FILES_TO_CHECK} | python3 "${CLANG_FORMAT_DIFF}" -p1 -style=file -binary "$CLANG_FORMAT") 50 | 51 | if [ -z "${FORMAT_DIFF}" ]; then 52 | echo "All source code in PR properly formatted." 53 | exit 0 54 | else 55 | echo "Found formatting errors!" 56 | echo "${FORMAT_DIFF}" 57 | exit 1 58 | fi 59 | -------------------------------------------------------------------------------- /utils/git-sync-deps: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright 2023 The Effcee Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import os 18 | import subprocess 19 | 20 | def run(cmd, directory, fail_ok=False, verbose=False): 21 | """Runs a command in a directory and returns its standard output stream. 22 | 23 | Captures and returns the standard error stream. 24 | 25 | Raises a RuntimeError if the command fails to launch or otherwise fails. 26 | """ 27 | if verbose: 28 | print(cmd) 29 | p = subprocess.Popen(cmd, 30 | cwd=directory, 31 | stdout=subprocess.PIPE) 32 | (stdout, _) = p.communicate() 33 | if p.returncode != 0 and not fail_ok: 34 | raise RuntimeError('Failed to run {} in {}'.format(cmd, directory)) 35 | return stdout 36 | 37 | # This is needed for interpreting the contents of the DEPS file. 38 | def Var(key): 39 | """Returns the value for the given key in the 'vars' dictionary""" 40 | return vars[key] 41 | 42 | # Get deps dictionary from the DEPS file 43 | with open('DEPS', mode='r', encoding='utf-8') as f: 44 | exec(''.join(f.readlines())) 45 | # Now 'deps' is a dictionary mapping relative subdirectory name 46 | # to a repo-and-commit string such as 'https://github.com/google/effcee@v2.20' 47 | 48 | for d in sorted(deps.keys()): 49 | (repo,commit) = deps[d].split('@') 50 | print("{} <-- {} {}".format(d,repo,commit)) 51 | os.makedirs(d, exist_ok=True) 52 | # Clone afresh if needed 53 | if not os.path.exists(os.path.join(d, '.git')): 54 | run(['git', 'clone', '--single-branch', repo,'.'], d, verbose=True) 55 | # Ensure there is a known-good remote 56 | if len(run(['git', 'remote', 'get-url', 'known-good'], d, fail_ok=True)) == 0: 57 | run(['git', 'remote', 'add', 'known-good', repo], d) 58 | # And it has the right remote URL 59 | run(['git', 'remote', 'set-url', 'known-good', repo], d) 60 | # Fetch if the commit isn't yet present 61 | if len(run(['git', 'rev-parse', '--verify', '--quiet', commit + '^{commit}'], 62 | d, 63 | fail_ok=True)) == 0: 64 | run(['git', 'fetch', 'known-good'], d, verbose=True) 65 | # Checkout the commit 66 | run(['git', 'checkout', commit], d) 67 | print() 68 | 69 | # vim: expandtab ts=4 sw=4 70 | --------------------------------------------------------------------------------