pax_global_header00006660000000000000000000000064136722010760014516gustar00rootroot0000000000000052 comment=33d438fb1939e94e5507d38dee9d999f60a03d96 effcee-2019.1/000077500000000000000000000000001367220107600130275ustar00rootroot00000000000000effcee-2019.1/.appveyor.yml000066400000000000000000000027401367220107600155000ustar00rootroot00000000000000# Windows Build Configuration for AppVeyor # http://www.appveyor.com/docs/appveyor-yml # version format version: "{build}" os: - Visual Studio 2017 - Visual Studio 2015 platform: - x64 configuration: - Debug - Release branches: only: - master matrix: fast_finish: true exclude: - os: Visual Studio 2015 configuration: Debug # scripts that run after cloning repository install: - git clone --depth=1 https://github.com/google/googletest.git third_party/googletest - git clone --depth=1 https://github.com/google/re2.git third_party/re2 - set PATH=c:\Python36;%PATH% before_build: - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" (call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86_amd64) - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" (call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x86_amd64) build: parallel: true # enable MSBuild parallel builds verbosity: minimal build_script: - mkdir build && cd build - cmake .. -DCMAKE_INSTALL_PREFIX=install -DRE2_BUILD_TESTING=OFF - cmake --build . --target install --config %CONFIGURATION% test_script: - ctest -C %CONFIGURATION% --output-on-failure notifications: - provider: Email to: - dneto@google.com subject: 'Effcee Windows Build #{{buildVersion}}: {{status}}' on_build_success: false on_build_failure: true on_build_status_changed: true effcee-2019.1/.clang-format000066400000000000000000000001401367220107600153750ustar00rootroot00000000000000--- Language: Cpp BasedOnStyle: Google DerivePointerAlignment: false PointerAlignment: Left ... effcee-2019.1/.gitignore000066400000000000000000000002111367220107600150110ustar00rootroot00000000000000build/ build-*/ out/ *.pyc *.swp *~ compile_commands.json .ycm_extra_conf.py cscope.* third_party/re2/ third_party/googletest/ .DS_Store effcee-2019.1/.travis.yml000066400000000000000000000024241367220107600151420ustar00rootroot00000000000000# Linux Build Configuration for Travis language: cpp os: - linux - osx # Use Ubuntu 14.04 LTS (Trusty) as the Linux testing environment. sudo: required dist: trusty env: - EFFCEE_BUILD_TYPE=Release - EFFCEE_BUILD_TYPE=Debug compiler: - clang - gcc matrix: fast_finish: true # Show final status immediately if a test fails. exclude: # Skip GCC builds on macOS. - os: osx compiler: gcc cache: apt: true branches: only: - master addons: apt: packages: - clang - ninja-build before_install: # Install ninja on macOS. - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install ninja; fi install: - if [[ "$TRAVIS_OS_NAME" == "linux" && "$CC" == "clang" ]]; then export CC=clang CXX=clang++; fi before_script: - git clone --depth=1 https://github.com/google/googletest third_party/googletest - git clone --depth=1 https://github.com/google/re2 third_party/re2 script: - mkdir build && cd build - cmake -DCMAKE_BUILD_TYPE=${EFFCEE_BUILD_TYPE:-Debug} -DRE2_BUILD_TESTING=OFF -GNinja ..; - ninja - ctest notifications: email: recipients: - dneto@google.com on_success: change on_failure: always effcee-2019.1/AUTHORS000066400000000000000000000004651367220107600141040ustar00rootroot00000000000000# This is the official list of Effcee authors for copyright purposes. # This file is distinct from the CONTRIBUTORS files. # See the latter for an explanation. # Names should be added to this file as: # Name or Organization # The email address is not required for organizations. Google Inc. effcee-2019.1/BUILD.bazel000066400000000000000000000033341367220107600147100ustar00rootroot00000000000000package( default_visibility = ["//visibility:public"], ) # Description: # # Effcee is a C++ library for stateful pattern matching of strings inspired by # LLVM's FileCheck. licenses(["notice"]) # Apache 2.0 exports_files([ "CHANGES", "LICENSE", ]) cc_library( name = "effcee", srcs = glob( ["effcee/*.cc"], exclude = ["effcee/*_test.cc"], ), hdrs = glob(["effcee/*.h"]), compatible_with = [ ], deps = [ "@com_googlesource_code_re2//:re2", ], ) # Tests cc_test( name = "check_test", srcs = ["effcee/check_test.cc"], deps = [ ":effcee", "@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest", ], ) cc_test( name = "cursor_test", srcs = ["effcee/cursor_test.cc"], deps = [ ":effcee", "@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest", ], ) cc_test( name = "diagnostic_test", srcs = ["effcee/diagnostic_test.cc"], deps = [ ":effcee", "@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest", ], ) cc_test( name = "match_test", srcs = ["effcee/match_test.cc"], deps = [ ":effcee", "@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest", ], ) cc_test( name = "options_test", srcs = ["effcee/options_test.cc"], deps = [ ":effcee", "@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest", ], ) cc_test( name = "result_test", srcs = ["effcee/result_test.cc"], deps = [ ":effcee", "@com_google_googletest//:gtest_main", "@com_google_googletest//:gtest", ], ) effcee-2019.1/CHANGES000066400000000000000000000016601367220107600140250ustar00rootroot00000000000000Revision history for Effcee v2019.1 2020-06-16 - Build/CI/release updates - GitHub repo: switch to 'main' branch, instead of 'master' - Respect CMAKE_INSTALL_LIBDIR in installed Cmake files - Travis-CI: On macOS run brew update first - Fixes: - protect make_unique with namespace v2019.0 2019-09-18 - Add optional tool effcee-fuzz to help run fuzzer cases. - Build updates - Add Bazel build rules - Add Clang warning -Wextra-semi - Require Python3 - Fix MinGW cross-compile - Fix tests to work with latest googletest - Fixes: - Fail parsing checks when regular expressions are invalid. - #23: Avoid StringPiece::as_string to enhance portability. v2018.1 2018-10-05 - Require CMake 3.1 or later - Require C++11 - Travis-CI testing uses stock clang, instead of clang-3.6 (which is old by now) v2018.0 2018-10-05 - Mature enough for production use by third party projects such as DXC and SPIRV-Tools. effcee-2019.1/CMakeLists.txt000066400000000000000000000022531367220107600155710ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1) project(effcee C CXX) enable_testing() set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) option(EFFCEE_BUILD_TESTING "Enable testing for Effcee" ON) if(${EFFCEE_BUILD_TESTING}) message(STATUS "Configuring Effcee to build tests.") if(MSVC) # Our tests use ::testing::Combine. Force the ability to use it, working # around googletest's possibly faulty compiler detection logic. # See https://github.com/google/googletest/issues/1352 add_definitions(-DGTEST_HAS_COMBINE=1) endif(MSVC) else() message(STATUS "Configuring Effcee to avoid building tests.") endif() option(EFFCEE_BUILD_SAMPLES "Enable building sample Effcee programs" ON) if(${EFFCEE_BUILD_SAMPLES}) message(STATUS "Configuring Effcee to build samples.") else() message(STATUS "Configuring Effcee to avoid building samples.") endif() # RE2 needs Pthreads on non-WIN32 set(CMAKE_THREAD_LIBS_INIT "") find_package(Threads) include(cmake/setup_build.cmake) include(cmake/utils.cmake) include(GNUInstallDirs) add_subdirectory(third_party) add_subdirectory(effcee) add_subdirectory(fuzzer) if(${EFFCEE_BUILD_SAMPLES}) add_subdirectory(examples) endif() effcee-2019.1/CONTRIBUTING.md000066400000000000000000000026031367220107600152610ustar00rootroot00000000000000# Contributing to Effcee Want to contribute? Great! First, read this page (including the small print at the end). Then, have a look at [`DEVELOPMENT.howto.md`](DEVELOPMENT.howto.md), which contains useful info to guide you along the way. ## Before you contribute Before we can use your code, you must sign the [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual?csw=1) (CLA), which you can do online. The CLA is necessary mainly because you own the copyright to your changes, even after your contribution becomes part of our codebase, so we need your permission to use and distribute your code. We also need to be sure of various other things -- for instance that you'll tell us if you know that your code infringes on other people's patents. You don't have to sign the CLA until after you've submitted your code for review and a member has approved it, but you must do it before we can put your code into our codebase. Before you start working on a larger contribution, you should get in touch with us first through the issue tracker with your idea so that we can help out and possibly guide you. Coordinating up front makes it much easier to avoid frustration later on. ## The small print Contributions made by corporations are covered by a different agreement than the one above, the Software Grant and Corporate Contributor License Agreement. effcee-2019.1/CONTRIBUTORS000066400000000000000000000010761367220107600147130ustar00rootroot00000000000000# People who have agreed to one of the CLAs and can contribute patches. # The AUTHORS file lists the copyright holders; this file # lists people. For example, Google employees are listed here # but not in AUTHORS, because Google holds the copyright. # # https://developers.google.com/open-source/cla/individual # https://developers.google.com/open-source/cla/corporate # # Names should be added to this file as: # Name Alan Baker Ehsan Nasiri David Neto Lei Zhang effcee-2019.1/DEVELOPMENT.howto.md000066400000000000000000000047551367220107600163050ustar00rootroot00000000000000# Developing for Effcee Thank you for considering Effcee development! Please make sure you review [`CONTRIBUTING.md`](CONTRIBUTING.md) for important preliminary info. ## Building Instructions for first-time building can be found in [`README.md`](README.md). Incremental build after a source change can be done using `bazel` or `ninja` (or `cmake --build`) and `ctest` exactly as in the first-time procedure. ## Issue tracking We use GitHub issues to track bugs, enhancement requests, and questions. See [the project's Issues page](https://github.com/google/effcee/issues). For all but the most trivial changes, we prefer that you file an issue before submitting a pull request. An issue gives us context for your change: what problem are you solving, and why. It also allows us to provide feedback on your proposed solution before you invest a lot of effort implementing it. ## Code reviews All submissions are subject to review via the GitHub pull review process. Reviews will cover: * *Correctness:* Does it work? Does it work in a multithreaded context? * *Testing:* New functionality should be accompanied by tests. * *Testability:* Can it easily be tested? This is proven with accompanying tests. * *Design:* Is the solution fragile? Does it fit with the existing code? Would it easily accommodate anticipated changes? * *Ease of use:* Can a client get their work done with a minimum of fuss? Are there unnecessarily surprising details? * *Consistency:* Does it follow the style guidelines and the rest of the code? Consistency reduces the work of future readers and maintainers. * *Portability:* Does it work in many environments? To respond to feedback, submit one or more *new* commits to the pull request branch. The project maintainer will normally clean up the submission by squashing feedback response commits. We maintain a linear commit history, so submission will be rebased onto master before merging. ## Testing There is a lot we won't say about testing. However: * Most tests should be small scale, i.e. unit tests. * Tests should run quickly. * A test should: * Check a single behaviour. This often corresponds to a use case. * Have a three phase structure: setup, action, check. ## Coding style For C++, we follow the [Google C++ style guide](https://google.github.io/styleguide/cppguide.html). Use `clang-format` to format the code. For our Python files, we aim to follow the [Google Python style guide](https://google.github.io/styleguide/pyguide.html). effcee-2019.1/LICENSE000066400000000000000000000261351367220107600140430ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. effcee-2019.1/README.md000066400000000000000000000237111367220107600143120ustar00rootroot00000000000000# Effcee [![Linux and OSX Build Status](https://travis-ci.org/google/effcee.svg)](https://travis-ci.org/google/effcee "Linux and OSX Build Status") Effcee is a C++ library for stateful pattern matching of strings, inspired by LLVM's [FileCheck][FileCheck] command. Effcee: - Is a library, so it can be used for quickly running tests in your own process. - Is largely compatible with FileCheck, so tests and test-writing skills are transferable. - Has few dependencies: - The C++11 standard library, and - [RE2][RE2] for regular expression matching. ## Example The following is from [examples/main.cc](examples/main.cc): ```C++ #include #include #include "effcee/effcee.h" // Checks standard input against the list of checks provided as command line // arguments. // // Example: // cat <sample_data.txt // Bees // Make // Delicious Honey // EOF // effcee-example > input_stream.rdbuf(); // Attempt to match. The input and checks arguments can be provided as // std::string or pointer to char. auto result = effcee::Match(input_stream.str(), checks_stream.str(), effcee::Options().SetChecksName("checks")); // Successful match result converts to true. if (result) { std::cout << "The input matched your check list!" << std::endl; } else { // Otherwise, you can get a status code and a detailed message. switch (result.status()) { case effcee::Result::Status::NoRules: std::cout << "error: Expected check rules as command line arguments\n"; break; case effcee::Result::Status::Fail: std::cout << "The input failed to match your check rules:\n"; break; default: break; } std::cout << result.message() << std::endl; return 1; } return 0; } ``` For more examples, see the matching tests in [effcee/match_test.cc](effcee/match_test.cc). ## Status Effcee is mature enough to be relied upon by [third party projects](#what-uses-effcee), but could be improved. What works: * All check types: CHECK, CHECK-NEXT, CHECK-SAME, CHECK-DAG, CHECK-LABEL, CHECK-NOT. * Check strings can contain: * fixed strings * regular expressions * variable definitions and uses * Setting a custom check prefix. * Accurate and helpful reporting of match failures. What is left to do: * Add an option to define shorthands for regular expressions. * For example, you could express that if the string `%%` appears where a regular expression is expected, then it expands to the regular expression for a local identifier in LLVM assembly language, i.e. `%[-a-zA-Z$._][-a-zA-Z$._0-9]*`. This enables you to write precise tests with less fuss. * Better error reporting for failure to parse the checks list. * Write a check language reference and tutorial. What is left to do, but lower priority: * Match full lines. * Strict whitespace. * Implicit check-not. * Variable scoping. ## Licensing and contributing Effcee is licensed under terms of the [Apache 2.0 license](LICENSE). If you are interested in contributing to this project, please see [`CONTRIBUTING.md`](CONTRIBUTING.md). This is not an official Google product (experimental or otherwise), it is just code that happens to be owned by Google. That may change if Effcee gains contributions from others. See the [`CONTRIBUTING.md`](CONTRIBUTING.md) file for more information. See also the [`AUTHORS`](AUTHORS) and [`CONTRIBUTORS`](CONTRIBUTORS) files. ## File organization - [`effcee`/](effcee) : library source code, and tests - `third_party/`: third party open source packages, downloaded separately - [`examples/`](examples): example programs Effcee depends on the [RE2][RE2] regular expression library. Effcee tests depend on [Googletest][Googletest] and [Python 3][Python]. In the following sections, `$SOURCE_DIR` is the directory containing the Effcee source code. ## Getting and building Effcee 1) Check out the source code: ```sh git clone https://github.com/google/effcee $SOURCE_DIR cd $SOURCE_DIR/third_party git clone https://github.com/google/googletest.git git clone https://github.com/google/re2.git cd $SOURCE_DIR/ ``` Note: There are two other ways to manage third party sources: - If you are building Effcee with Bazel (https://bazel.build), you do not need to clone the repositories for `googletest` and `re2`. They will be automatically downloaded by Bazel during build. Bazel will suggest adding `sha256` attributes to each repository rule to get hermetic builds (these notices are safe to ignore if you are not interested in hermetic builds). - If you are building Effcee as part of a larger CMake-based project, add the RE2 and `googletest` projects before adding Effcee. - Otherwise, you can set CMake variables to point to third party sources if they are located somewhere else. See the [Build options](#build-options) below. 2) Ensure you have the requisite tools -- see the tools subsection below. 3) Decide where to place the build output. In the following steps, we'll call it `$BUILD_DIR`. Any new directory should work. We recommend building outside the source tree, but it is also common to build in a (new) subdirectory of `$SOURCE_DIR`, such as `$SOURCE_DIR/build`. 4a) Build and test with Ninja on Linux or Windows: ```sh cd $BUILD_DIR cmake -GNinja -DCMAKE_BUILD_TYPE={Debug|Release|RelWithDebInfo} $SOURCE_DIR ninja ctest ``` 4b) Or build and test with MSVC on Windows: ```sh cd $BUILD_DIR cmake $SOURCE_DIR cmake --build . --config {Release|Debug|MinSizeRel|RelWithDebInfo} ctest -C {Release|Debug|MinSizeRel|RelWithDebInfo} ``` 4c) Or build with MinGW on Linux for Windows: (Skip building threaded unit tests due to [Googletest bug 606](https://github.com/google/googletest/issues/606)) ```sh cd $BUILD_DIR cmake -GNinja -DCMAKE_BUILD_TYPE={Debug|Release|RelWithDebInfo} $SOURCE_DIR \ -DCMAKE_TOOLCHAIN_FILE=$SOURCE_DIR/cmake/linux-mingw-toolchain.cmake \ -Dgtest_disable_pthreads=ON ninja ``` 4d) Or build with Bazel on Linux: ```sh cd $SOURCE_DIR bazel build -c opt :all ``` After a successful build, you should have a `libeffcee` library under the `$BUILD_DIR/effcee/` directory (or `$SOURCE_DIR/bazel-bin` when building with Bazel). The default behavior on MSVC is to link with the static CRT. If you would like to change this behavior `-DEFFCEE_ENABLE_SHARED_CRT` may be passed on the cmake configure line. ### Tests By default, Effcee registers two tests with `ctest`: * `effcee-test`: All library tests, based on Googletest. * `effcee-example`: Executes the example executable with sample inputs. Running `ctest` without arguments will run the tests for Effcee as well as for RE2. You can disable Effcee's tests by using `-DEFFCEE_BUILD_TESTING=OFF` at configuration time: ```sh cmake -GNinja -DEFFCEE_BUILD_TESTING=OFF ... ``` The RE2 tests run much longer, so if you're working on Effcee alone, we suggest limiting ctest to tests with prefix `effcee`: ctest -R effcee Alternately, you can turn off RE2 tests entirely by using `-DRE2_BUILD_TESTING=OFF` at configuration time: ```sh cmake -GNinja -DRE2_BUILD_TESTING=OFF ... ``` ### Tools you'll need For building, testing, and profiling Effcee, the following tools should be installed regardless of your OS: - A compiler supporting C++11. - [CMake][CMake]: for generating compilation targets. - [Python 3][Python]: for a test script. On Linux, if cross compiling to Windows: - [MinGW][MinGW]: A GCC-based cross compiler targeting Windows so that generated executables use the Microsoft C runtime libraries. On Windows, the following tools should be installed and available on your path: - Visual Studio 2015 or later. Previous versions of Visual Studio are not usable with RE2 or Googletest. - Git - including the associated tools, Bash, `diff`. ### Build options Third party source locations: - `EFFCEE_GOOGLETEST_DIR`: Location of `googletest` sources, if not under `third_party`. - `EFFCEE_RE2_DIR`: Location of `re2` sources, if not under `third_party`. - `EFFCEE_THIRD_PARTY_ROOT_DIR`: Alternate location for `googletest` and `re2` subdirectories. This is used if the sources are not located under the `third_party` directory, and if the previous two variables are not set. Compilation options: - `DISABLE_RTTI`. Disable runtime type information. Default is enabled. - `DISABLE_EXCEPTIONS`. Disable exceptions. Default is enabled. - `EFFCEE_ENABLE_SHARED_CRT`. See above. Controlling samples and tests: - `EFFCEE_BUILD_SAMPLES`. Should Effcee examples be built? Defaults to `ON`. - `EFFCEE_BUILD_TESTING`. Should Effcee tests be built? Defaults to `ON`. - `RE2_BUILD_TESTING`. Should RE2 tests be built? Defaults to `ON`. ## Bug tracking We track bugs using GitHub -- click on the "Issues" button on [the project's GitHub page](https://github.com/google/effcee). ## What uses Effcee? - [Tests](https://github.com/Microsoft/DirectXShaderCompiler/tree/master/tools/clang/test/CodeGenSPIRV) for SPIR-V code generation in the [DXC][DXC] HLSL compiler. - Tests for [SPIRV-Tools][SPIRV-Tools] ## References [CMake]: https://cmake.org/ [DXC]: https://github.com/Microsoft/DirectXShaderCompiler [FileCheck]: http://llvm.org/docs/CommandGuide/FileCheck.html [Googletest]: https://github.com/google/googletest [MinGW]: http://www.mingw.org/ [Python]: https://www.python.org/ [RE2]: https://github.com/google/re2 [SPIRV-Tools]: https://github.com/KhronosGroup/SPIRV-Tools effcee-2019.1/WORKSPACE000066400000000000000000000010711367220107600143070ustar00rootroot00000000000000workspace(name = "effcee") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") http_archive( name = "rules_cc", strip_prefix = "rules_cc-master", urls = ["https://github.com/bazelbuild/rules_cc/archive/master.zip"], ) http_archive( name = "com_google_googletest", strip_prefix = "googletest-master", urls = ["https://github.com/google/googletest/archive/master.zip"], ) http_archive( name = "com_googlesource_code_re2", strip_prefix = "re2-master", urls = ["https://github.com/google/re2/archive/master.zip"], ) effcee-2019.1/cmake/000077500000000000000000000000001367220107600141075ustar00rootroot00000000000000effcee-2019.1/cmake/linux-mingw-toolchain.cmake000066400000000000000000000025731367220107600213540ustar00rootroot00000000000000# Copyright 2017 The Effcee Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. SET(CMAKE_SYSTEM_NAME Windows) set(MINGW_COMPILER_PREFIX "i686-w64-mingw32" CACHE STRING "What compiler prefix to use for mingw") set(MINGW_SYSROOT "/usr/${MINGW_COMPILER_PREFIX}" CACHE STRING "What sysroot to use for mingw") # Which compilers to use for C and C++ find_program(CMAKE_RC_COMPILER NAMES ${MINGW_COMPILER_PREFIX}-windres) find_program(CMAKE_C_COMPILER NAMES ${MINGW_COMPILER_PREFIX}-gcc) find_program(CMAKE_CXX_COMPILER NAMES ${MINGW_COMPILER_PREFIX}-g++) SET(CMAKE_FIND_ROOT_PATH ${MINGW_SYSROOT}) # Adjust the default behaviour of the FIND_XXX() commands: # Search headers and libraries in the target environment; search # programs in the host environment. set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) effcee-2019.1/cmake/setup_build.cmake000066400000000000000000000037211367220107600174330ustar00rootroot00000000000000# Copyright 2017 The Effcee Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. if(NOT COMMAND find_host_package) macro(find_host_package) find_package(${ARGN}) endmacro() endif() find_host_package(PythonInterp 3 REQUIRED) option(DISABLE_RTTI "Disable RTTI in builds") if(DISABLE_RTTI) if(UNIX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") endif(UNIX) endif(DISABLE_RTTI) option(DISABLE_EXCEPTIONS "Disables exceptions in builds") if(DISABLE_EXCEPTIONS) if(UNIX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions") endif(UNIX) endif(DISABLE_EXCEPTIONS) if(WIN32) # Ensure that gmock compiles the same as the rest of the code, otherwise # failures will occur. set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) endif(WIN32) if(WIN32) # On Windows, CMake by default compiles with the shared CRT. # Default it to the static CRT. option(EFFCEE_ENABLE_SHARED_CRT "Use the shared CRT with MSVC instead of the static CRT" ${EFFCEE_ENABLE_SHARED_CRT}) if (NOT EFFCEE_ENABLE_SHARED_CRT) if(MSVC) # Link executables statically by replacing /MD with /MT everywhere. foreach(flag_var CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) if(${flag_var} MATCHES "/MD") string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") endif(${flag_var} MATCHES "/MD") endforeach(flag_var) endif(MSVC) endif(NOT EFFCEE_ENABLE_SHARED_CRT) endif(WIN32) effcee-2019.1/cmake/utils.cmake000066400000000000000000000051031367220107600162500ustar00rootroot00000000000000# Copyright 2017 The Effcee Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Utility functions function(effcee_default_c_compile_options TARGET) if (NOT "${MSVC}") target_compile_options(${TARGET} PRIVATE -Wall -Werror) if (ENABLE_CODE_COVERAGE) # The --coverage option is a synonym for -fprofile-arcs -ftest-coverage # when compiling. target_compile_options(${TARGET} PRIVATE -g -O0 --coverage) # The --coverage option is a synonym for -lgcov when linking for gcc. # For clang, it links in a different library, libclang_rt.profile, which # requires clang to be built with compiler-rt. target_link_libraries(${TARGET} PRIVATE --coverage) endif() if (NOT EFFCEE_ENABLE_SHARED_CRT) if (WIN32) # For MinGW cross compile, statically link to the libgcc runtime. # But it still depends on MSVCRT.dll. set_target_properties(${TARGET} PROPERTIES LINK_FLAGS "-static -static-libgcc") endif(WIN32) endif(NOT EFFCEE_ENABLE_SHARED_CRT) if (UNIX AND NOT MINGW) target_link_libraries(${TARGET} PUBLIC -pthread) endif() if (${CMAKE_C_COMPILER_ID} MATCHES "Clang") target_compile_options(${TARGET} PRIVATE -Wextra-semi) endif() else() # disable warning C4800: 'int' : forcing value to bool 'true' or 'false' # (performance warning) target_compile_options(${TARGET} PRIVATE /wd4800) endif() endfunction(effcee_default_c_compile_options) function(effcee_default_compile_options TARGET) effcee_default_c_compile_options(${TARGET}) if (NOT "${MSVC}") # RE2's public header requires C++11. So publicly required C++11 target_compile_options(${TARGET} PUBLIC -std=c++11) if (NOT EFFCEE_ENABLE_SHARED_CRT) if (WIN32) # For MinGW cross compile, statically link to the C++ runtime. # But it still depends on MSVCRT.dll. set_target_properties(${TARGET} PROPERTIES LINK_FLAGS "-static -static-libgcc -static-libstdc++") endif(WIN32) endif(NOT EFFCEE_ENABLE_SHARED_CRT) endif() endfunction(effcee_default_compile_options) effcee-2019.1/effcee/000077500000000000000000000000001367220107600142445ustar00rootroot00000000000000effcee-2019.1/effcee/CMakeLists.txt000066400000000000000000000021711367220107600170050ustar00rootroot00000000000000add_library(effcee check.cc match.cc) effcee_default_compile_options(effcee) # We need to expose RE2's StringPiece. target_include_directories(effcee PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/.. ${EFFCEE_RE2_DIR}) target_link_libraries(effcee PUBLIC re2 ${CMAKE_THREADS_LIB_INIT}) # TODO(dneto): Avoid installing gtest and gtest_main. ?! install( FILES effcee.h DESTINATION include/effcee) install(TARGETS effcee LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) if(EFFCEE_BUILD_TESTING) add_executable(effcee-test check_test.cc cursor_test.cc diagnostic_test.cc match_test.cc options_test.cc result_test.cc) effcee_default_compile_options(effcee-test) target_include_directories(effcee-test PRIVATE ${gmock_SOURCE_DIR}/include ${gtest_SOURCE_DIR}/include) target_link_libraries(effcee-test PRIVATE effcee gmock gtest_main) add_test(NAME effcee-test COMMAND effcee-test) endif(EFFCEE_BUILD_TESTING) effcee-2019.1/effcee/check.cc000066400000000000000000000232101367220107600156260ustar00rootroot00000000000000// Copyright 2017 The Effcee Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "check.h" #include #include #include #include #include #include #include "cursor.h" #include "effcee.h" #include "make_unique.h" #include "to_string.h" using Status = effcee::Result::Status; using StringPiece = effcee::StringPiece; using Type = effcee::Check::Type; namespace { // Returns a table of suffix to type mappings. const std::vector>& TypeStringTable() { static std::vector> type_str_table{ {"", Type::Simple}, {"-NEXT", Type::Next}, {"-SAME", Type::Same}, {"-DAG", Type::DAG}, {"-LABEL", Type::Label}, {"-NOT", Type::Not}}; return type_str_table; } // Returns the Check::Type value matching the suffix part of a check rule // prefix. Assumes |suffix| is valid. Type TypeForSuffix(StringPiece suffix) { const auto& type_str_table = TypeStringTable(); const auto pair_iter = std::find_if(type_str_table.begin(), type_str_table.end(), [suffix](const std::pair& elem) { return suffix == elem.first; }); assert(pair_iter != type_str_table.end()); return pair_iter->second; } } // namespace namespace effcee { int Check::Part::CountCapturingGroups() { if (type_ == Type::Regex) return RE2(param_).NumberOfCapturingGroups(); if (type_ == Type::VarDef) return RE2(expression_).NumberOfCapturingGroups(); return 0; } Check::Check(Type type, StringPiece param) : type_(type), param_(param) { parts_.push_back(effcee::make_unique(Part::Type::Fixed, param)); } bool Check::Part::MightMatch(const VarMapping& vars) const { return type_ != Type::VarUse || vars.find(ToString(VarUseName())) != vars.end(); } std::string Check::Part::Regex(const VarMapping& vars) const { switch (type_) { case Type::Fixed: return RE2::QuoteMeta(param_); case Type::Regex: return ToString(param_); case Type::VarDef: return std::string("(") + ToString(expression_) + ")"; case Type::VarUse: { auto where = vars.find(ToString(VarUseName())); if (where != vars.end()) { // Return the escaped form of the current value of the variable. return RE2::QuoteMeta((*where).second); } else { // The variable is not yet set. Should not get here. return ""; } } } return ""; // Unreachable. But we need to satisfy GCC. } bool Check::Matches(StringPiece* input, StringPiece* captured, VarMapping* vars) const { if (parts_.empty()) return false; for (auto& part : parts_) { if (!part->MightMatch(*vars)) return false; } std::unordered_map var_def_indices; std::ostringstream consume_regex; int num_captures = 1; // The outer capture. for (auto& part : parts_) { consume_regex << part->Regex(*vars); const auto var_def_name = part->VarDefName(); if (!var_def_name.empty()) { var_def_indices[num_captures++] = ToString(var_def_name); } num_captures += part->NumCapturingGroups(); } std::unique_ptr captures(new StringPiece[num_captures]); const bool matched = RE2(consume_regex.str()) .Match(*input, 0, input->size(), RE2::UNANCHORED, captures.get(), num_captures); if (matched) { *captured = captures[0]; input->remove_prefix(captured->end() - input->begin()); // Update the variable mapping. for (auto& var_def_index : var_def_indices) { const int index = var_def_index.first; (*vars)[var_def_index.second] = ToString(captures[index]); } } return matched; } namespace { // Returns a Result and a parts list for the given pattern. This splits out // regular expressions as delimited by {{ and }}, and also variable uses and // definitions. This can fail when a regular expression is invalid. std::pair PartsForPattern(StringPiece pattern) { Check::Parts parts; StringPiece fixed, regex, var; using Type = Check::Part::Type; while (!pattern.empty()) { const auto regex_start = pattern.find("{{"); const auto regex_end = pattern.find("}}"); const auto var_start = pattern.find("[["); const auto var_end = pattern.find("]]"); const bool regex_exists = regex_start < regex_end && regex_end < StringPiece::npos; const bool var_exists = var_start < var_end && var_end < StringPiece::npos; if (regex_exists && (!var_exists || regex_start < var_start)) { const auto consumed = RE2::Consume(&pattern, "(.*?){{(.*?)}}", &fixed, ®ex); if (!consumed) { assert(consumed && "Did not make forward progress for regex in check rule"); } if (!fixed.empty()) { parts.emplace_back( effcee::make_unique(Type::Fixed, fixed)); } if (!regex.empty()) { parts.emplace_back( effcee::make_unique(Type::Regex, regex)); if (parts.back()->NumCapturingGroups() < 0) { return std::make_pair( Result(Result::Status::BadRule, std::string("invalid regex: ") + ToString(regex)), Check::Parts()); } } } else if (var_exists && (!regex_exists || var_start < regex_start)) { const auto consumed = RE2::Consume(&pattern, "(.*?)\\[\\[(.*?)\\]\\]", &fixed, &var); if (!consumed) { assert(consumed && "Did not make forward progress for var in check rule"); } if (!fixed.empty()) { parts.emplace_back( effcee::make_unique(Type::Fixed, fixed)); } if (!var.empty()) { auto colon = var.find(":"); // A colon at the end is useless anyway, so just make it a variable // use. if (colon == StringPiece::npos || colon == var.size() - 1) { parts.emplace_back( effcee::make_unique(Type::VarUse, var)); } else { StringPiece name = var.substr(0, colon); StringPiece expression = var.substr(colon + 1, StringPiece::npos); parts.emplace_back(effcee::make_unique( Type::VarDef, var, name, expression)); if (parts.back()->NumCapturingGroups() < 0) { return std::make_pair( Result( Result::Status::BadRule, std::string("invalid regex in variable definition for ") + ToString(name) + ": " + ToString(expression)), Check::Parts()); } } } } else { // There is no regex, no var def, no var use. Must be a fixed string. parts.push_back(effcee::make_unique(Type::Fixed, pattern)); break; } } return std::make_pair(Result(Result::Status::Ok), std::move(parts)); } } // namespace std::pair ParseChecks(StringPiece str, const Options& options) { // Returns a pair whose first member is a result constructed from the // given status and message, and the second member is an empy pattern. auto failure = [](Status status, StringPiece message) { return std::make_pair(Result(status, message), CheckList{}); }; if (options.prefix().size() == 0) return failure(Status::BadOption, "Rule prefix is empty"); if (RE2::FullMatch(options.prefix(), "\\s+")) return failure(Status::BadOption, "Rule prefix is whitespace. That's silly."); CheckList check_list; const auto quoted_prefix = RE2::QuoteMeta(options.prefix()); // Match the following parts: // .*? - Text that is not the rule prefix // quoted_prefix - A Simple Check prefix // (-NEXT|-SAME)? - An optional check type suffix. Two shown here. // : - Colon // \s* - Whitespace // (.*?) - Captured parameter // \s* - Whitespace // $ - End of line const RE2 regexp(std::string(".*?") + quoted_prefix + "(-NEXT|-SAME|-DAG|-LABEL|-NOT)?" ":\\s*(.*?)\\s*$"); Cursor cursor(str); while (!cursor.Exhausted()) { const auto line = cursor.RestOfLine(); StringPiece matched_param; StringPiece suffix; if (RE2::PartialMatch(line, regexp, &suffix, &matched_param)) { const Type type = TypeForSuffix(suffix); auto parts = PartsForPattern(matched_param); if (!parts.first) return std::make_pair(parts.first, CheckList()); check_list.push_back(Check(type, matched_param, std::move(parts.second))); } cursor.AdvanceLine(); } if (check_list.empty()) { return failure( Status::NoRules, std::string("No check rules specified. Looking for prefix ") + options.prefix()); } if (check_list[0].type() == Type::Same) { return failure(Status::BadRule, std::string(options.prefix()) + "-SAME can't be the first check rule"); } return std::make_pair(Result(Result::Status::Ok), check_list); } } // namespace effcee effcee-2019.1/effcee/check.h000066400000000000000000000162271367220107600155020ustar00rootroot00000000000000// Copyright 2017 The Effcee Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef EFFCEE_CHECK_H #define EFFCEE_CHECK_H #include #include #include #include #include #include "effcee.h" #include "make_unique.h" namespace effcee { // A mapping from a name to a string value. using VarMapping = std::unordered_map; // A single check indicating something to be matched. // // A _positive_ check is _resolved_ when its parameter is matches a part of the // in the input text. A _negative_ check is _resolved_ when its parameter does // _not_ match a section of the input between context-dependent start and end // points. class Check { public: // The type Determines when the check is satisfied. The Not type denotes // a negative check. The other types denote positive checks. enum class Type { Simple, // Matches a string. Next, // Matches a string, on the line following previous match. Same, // Matches a string, on the same line as the previous metch. DAG, // Matches a string, unordered with respect to other Label, // Like Simple, but resets local variables. Not, // Given string is not found before next positive match. }; // A Part is a contiguous segment of the check pattern. A part is // distinguished by how it matches against input. class Part { public: enum class Type { Fixed, // A fixed string: characters are matched exactly, in sequence. Regex, // A regular expression VarDef, // A variable definition VarUse, // A variable use }; Part(Type type, StringPiece param) : type_(type), param_(param), name_(), expression_(), num_capturing_groups_(CountCapturingGroups()) {} // A constructor for a VarDef variant. Part(Type type, StringPiece param, StringPiece name, StringPiece expr) : type_(type), param_(param), name_(name), expression_(expr), num_capturing_groups_(CountCapturingGroups()) {} // Returns true if this part might match a target string. The only case where // this is false is for a VarUse part where the variable is not yet defined. bool MightMatch(const VarMapping& vars) const; // Returns a regular expression to match this part, given a mapping of // variable names to values. If this part is a fixed string or variable use // then quoting has been applied. std::string Regex(const VarMapping& vars) const; // Returns number of capturing subgroups in the regex for a Regex or VarDef // part, and 0 for other parts. int NumCapturingGroups() const { return num_capturing_groups_; } // If this is a VarDef, then returns the name of the variable. Otherwise // returns an empty string. StringPiece VarDefName() const { return name_; } // If this is a VarUse, then returns the name of the variable. Otherwise // returns an empty string. StringPiece VarUseName() const { return type_ == Type::VarUse ? param_ : ""; } private: // Computes the number of capturing groups in this part. This is zero // for Fixed and VarUse parts. int CountCapturingGroups(); // The part type. Type type_; // The part parameter. For a Regex, VarDef, and VarUse, this does not // have the delimiters. StringPiece param_; // For a VarDef, the name of the variable. StringPiece name_; // For a VarDef, the regex matching the new value for the variable. StringPiece expression_; // The number of capturing subgroups in the regex for a Regex or VarDef // part, and 0 for other kinds of parts. int num_capturing_groups_; }; using Parts = std::vector>; // MSVC needs a default constructor. However, a default-constructed Check // instance can't be used for matching. Check() : type_(Type::Simple) {} // Construct a Check object of the given type and fixed parameter string. // In particular, this retains a StringPiece reference to the |param| // contents, so that string storage should remain valid for the duration // of this object. Check(Type type, StringPiece param); // Construct a Check object of the given type, with given parameter string // and specified parts. Check(Type type, StringPiece param, Parts&& parts) : type_(type), param_(param), parts_(std::move(parts)) {} // Move constructor. Check(Check&& other) : type_(other.type_), param_(other.param_) { parts_.swap(other.parts_); } // Copy constructor. Check(const Check& other) : type_(other.type_), param_(other.param_) { for (const auto& part : other.parts_) { parts_.push_back(effcee::make_unique(*part)); } } // Copy and move assignment. Check& operator=(Check other) { type_ = other.type_; param_ = other.param_; std::swap(parts_, other.parts_); return *this; } // Accessors. Type type() const { return type_; } StringPiece param() const { return param_; } const Parts& parts() const { return parts_; } // Tries to match the given string, using |vars| as the variable mapping // context. A variable use, e.g. '[[X]]', matches the current value for // that variable in vars, 'X' in this case. A variable definition, // e.g. '[[XYZ:[0-9]+]]', will match against the regex provdided after the // colon. If successful, returns true, advances |str| past the matched // portion, saves the captured substring in |captured|, and sets the value // of named variables in |vars| with the strings they matched. Otherwise // returns false and does not update |str| or |captured|. Assumes this // instance is not default-constructed. bool Matches(StringPiece* str, StringPiece* captured, VarMapping* vars) const; private: // The type of check. Type type_; // The parameter as given in user input, if any. StringPiece param_; // The parameter, broken down into parts. Parts parts_; }; // Equality operator for Check. inline bool operator==(const Check& lhs, const Check& rhs) { return lhs.type() == rhs.type() && lhs.param() == rhs.param(); } // Inequality operator for Check. inline bool operator!=(const Check& lhs, const Check& rhs) { return !(lhs == rhs); } using CheckList = std::vector; // Parses |checks_string|, returning a Result status object and the sequence // of recognized checks, taking |options| into account. The result status // object indicates success, or failure with a message. // TODO(dneto): Only matches simple checks for now. std::pair ParseChecks(StringPiece checks_string, const Options& options); } // namespace effcee #endif effcee-2019.1/effcee/check_test.cc000066400000000000000000000341121367220107600166700ustar00rootroot00000000000000// Copyright 2017 The Effcee Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include "gmock/gmock.h" #include "check.h" namespace { using effcee::Check; using effcee::Options; using effcee::CheckList; using effcee::ParseChecks; using effcee::Result; using effcee::StringPiece; using ::testing::Combine; using ::testing::Eq; using ::testing::HasSubstr; using ::testing::ValuesIn; using Part = effcee::Check::Part; using Status = effcee::Result::Status; using Type = Check::Type; using VarMapping = effcee::VarMapping; // Check class // Returns a vector of all Check types. std::vector AllTypes() { return {Type::Simple, Type::Next, Type::Same, Type::DAG, Type::Label, Type::Not}; } using CheckTypeTest = ::testing::TestWithParam; TEST_P(CheckTypeTest, ConstructWithAnyType) { Check check(GetParam(), ""); EXPECT_THAT(check.type(), Eq(GetParam())); } INSTANTIATE_TEST_SUITE_P(AllTypes, CheckTypeTest, ValuesIn(AllTypes())); using CheckParamTest = ::testing::TestWithParam; TEST_P(CheckParamTest, ConstructWithSampleParamValue) { Check check(Type::Simple, GetParam()); // The contents are the same. EXPECT_THAT(check.param(), Eq(GetParam())); // The referenced storage is the same. EXPECT_THAT(check.param().data(), Eq(GetParam().data())); } INSTANTIATE_TEST_SUITE_P(SampleParams, CheckParamTest, ValuesIn(std::vector{ "", "a b c", "The wind {{in}} the willows\n", "Bring me back to the mountains of yore."})); // Equality operator TEST(CheckEqualityTest, TrueWhenAllComponentsSame) { EXPECT_TRUE(Check(Type::Simple, "abc") == Check(Type::Simple, "abc")); } TEST(CheckEqualityTest, FalseWhenTypeDifferent) { EXPECT_FALSE(Check(Type::Simple, "abc") == Check(Type::Next, "abc")); } TEST(CheckEqualityTest, FalseWhenParamDifferent) { EXPECT_FALSE(Check(Type::Simple, "abc") == Check(Type::Simple, "def")); } // Inequality operator TEST(CheckInequalityTest, FalseWhenAllComponentsSame) { EXPECT_FALSE(Check(Type::Simple, "abc") != Check(Type::Simple, "abc")); } TEST(CheckInequalityTest, TrueWhenTypeDifferent) { EXPECT_TRUE(Check(Type::Simple, "abc") != Check(Type::Next, "abc")); } TEST(CheckInequalityTest, TrueWhenParamDifferent) { EXPECT_TRUE(Check(Type::Simple, "abc") != Check(Type::Simple, "def")); } // ParseChecks free function TEST(ParseChecks, FreeFunctionLinks) { std::pair parsed(ParseChecks("", Options())); } TEST(ParseChecks, FailWhenRulePrefixIsEmpty) { const auto parsed(ParseChecks("CHECK: now", Options().SetPrefix(""))); const Result& result = parsed.first; const CheckList& pattern = parsed.second; EXPECT_THAT(result.status(), Eq(Status::BadOption)); EXPECT_THAT(result.message(), Eq("Rule prefix is empty")); EXPECT_THAT(pattern.size(), Eq(0)); } TEST(ParseChecks, FailWhenRulePrefixIsWhitespace) { const auto parsed(ParseChecks("CHECK: now", Options().SetPrefix("\t\n "))); const Result& result = parsed.first; const CheckList& pattern = parsed.second; EXPECT_THAT(result.status(), Eq(Status::BadOption)); EXPECT_THAT(result.message(), Eq("Rule prefix is whitespace. That's silly.")); EXPECT_THAT(pattern.size(), Eq(0)); } TEST(ParseChecks, FailWhenChecksAbsent) { const auto parsed(ParseChecks("no checks", Options())); const Result& result = parsed.first; const CheckList& pattern = parsed.second; EXPECT_THAT(result.status(), Eq(Status::NoRules)); EXPECT_THAT(result.message(), Eq("No check rules specified. Looking for prefix CHECK")); EXPECT_THAT(pattern.size(), Eq(0)); } TEST(ParseChecks, FailWhenChecksAbsentWithCustomPrefix) { const auto parsed(ParseChecks("CHECK: now", Options().SetPrefix("FOO"))); const Result& result = parsed.first; const CheckList& pattern = parsed.second; EXPECT_THAT(result.status(), Eq(Status::NoRules)); EXPECT_THAT(result.message(), Eq("No check rules specified. Looking for prefix FOO")); EXPECT_THAT(pattern.size(), Eq(0)); } TEST(ParseChecks, FindSimpleCheck) { const auto parsed = ParseChecks("CHECK: now", Options()); EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); EXPECT_THAT(parsed.second, Eq(CheckList({Check(Type::Simple, "now")}))); } TEST(ParseChecks, FindSimpleCheckWithCustomPrefix) { const auto parsed = ParseChecks("FOO: how", Options().SetPrefix("FOO")); EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); EXPECT_THAT(parsed.second, Eq(CheckList({Check(Type::Simple, "how")}))); } TEST(ParseChecks, FindSimpleCheckWithCustomPrefixHavingRegexpMetachars) { const auto parsed = ParseChecks("[::alpha::]^\\d: how", Options().SetPrefix("[::alpha::]^\\d")); EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); EXPECT_THAT(parsed.second, Eq(CheckList({Check(Type::Simple, "how")}))); } TEST(ParseChecks, FindSimpleCheckPartwayThroughLine) { const auto parsed = ParseChecks("some other garbageCHECK: now", Options()); EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); EXPECT_THAT(parsed.second, Eq(CheckList({Check(Type::Simple, "now")}))); } TEST(ParseChecks, FindSimpleCheckCheckListWithoutSurroundingWhitespace) { const auto parsed = ParseChecks("CHECK:now", Options()); EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); EXPECT_THAT(parsed.second, Eq(CheckList({Check(Type::Simple, "now")}))); } TEST(ParseChecks, FindSimpleCheckCheckListWhileStrippingSurroundingWhitespace) { const auto parsed = ParseChecks("CHECK: \t now\t\t ", Options()); EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); EXPECT_THAT(parsed.second, Eq(CheckList({Check(Type::Simple, "now")}))); } TEST(ParseChecks, FindSimpleCheckCountsLinesCorrectly) { const auto parsed = ParseChecks("\n\nCHECK: now", Options()); EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); EXPECT_THAT(parsed.second, Eq(CheckList({Check(Type::Simple, "now")}))); } TEST(ParseChecks, FindSimpleChecksOnSeparateLines) { const auto parsed = ParseChecks("CHECK: now\n\n\nCHECK: and \n CHECK: then", Options()); EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); EXPECT_THAT(parsed.second, Eq(CheckList({Check(Type::Simple, "now"), Check(Type::Simple, "and"), Check(Type::Simple, "then")}))); } TEST(ParseChecks, FindSimpleChecksOnlyOncePerLine) { const auto parsed = ParseChecks("CHECK: now CHECK: then", Options()); EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); EXPECT_THAT(parsed.second, Eq(CheckList({Check(Type::Simple, "now CHECK: then")}))); } // Test parsing of the different check rule types. using ParseChecksTypeTest = ::testing::TestWithParam< std::tuple>>; TEST_P(ParseChecksTypeTest, Successful) { const auto& prefix = std::get<0>(GetParam()); const auto& type_str = std::get<0>(std::get<1>(GetParam())); const Type& type = std::get<1>(std::get<1>(GetParam())); // A CHECK-SAME rule can't appear first, so insert a CHECK: rule first. const std::string input = prefix + ": here\n" + prefix + type_str + ": now"; const auto parsed = ParseChecks(input, Options().SetPrefix(prefix)); EXPECT_THAT(parsed.first.status(), Eq(Status::Ok)); EXPECT_THAT(parsed.second, Eq(CheckList({Check(Type::Simple, "here"), Check(type, "now")}))); } // Returns a vector of pairs. Each pair has first member being a check type // suffix, and the second member is the corresponding check type. std::vector> AllCheckTypesAsPairs() { return { {"", Type::Simple}, {"-NEXT", Type::Next}, {"-SAME", Type::Same}, {"-DAG", Type::DAG}, {"-LABEL", Type::Label}, {"-NOT", Type::Not}, }; } INSTANTIATE_TEST_SUITE_P(AllCheckTypes, ParseChecksTypeTest, Combine(ValuesIn(std::vector{"CHECK", "FOO"}), ValuesIn(AllCheckTypesAsPairs()))); using ParseChecksTypeFailTest = ::testing::TestWithParam< std::tuple>>; // This is just one way to fail. TEST_P(ParseChecksTypeFailTest, FailureWhenNoColon) { const auto& prefix = std::get<0>(GetParam()); const auto& type_str = std::get<0>(std::get<1>(GetParam())); const std::string input = prefix + type_str + "BAD now"; const auto parsed = ParseChecks(input, Options().SetPrefix(prefix)); EXPECT_THAT(parsed.first.status(), Eq(Status::NoRules)); EXPECT_THAT(parsed.second, Eq(CheckList{})); } INSTANTIATE_TEST_SUITE_P(AllCheckTypes, ParseChecksTypeFailTest, Combine(ValuesIn(std::vector{"CHECK", "FOO"}), ValuesIn(AllCheckTypesAsPairs()))); TEST(ParseChecks, BadRegexpMatchTrailingSlashFails) { const auto parsed = ParseChecks("CHECK: {{\\}}", Options()); EXPECT_THAT(parsed.first.status(), Eq(Status::BadRule)); EXPECT_THAT(parsed.first.message(), HasSubstr("invalid regex: \\")); EXPECT_THAT(parsed.second, Eq(CheckList({}))); } TEST(ParseChecks, BadRegexpVardefUnboundOptionalFails) { const auto parsed = ParseChecks("CHECK: [[VAR:?]]", Options()); EXPECT_THAT(parsed.first.status(), Eq(Status::BadRule)); EXPECT_THAT(parsed.first.message(), HasSubstr("invalid regex in variable definition for VAR: ?")); EXPECT_THAT(parsed.second, Eq(CheckList({}))); } TEST(ParseChecks, CheckSameCantBeFirst) { const auto parsed = ParseChecks("CHECK-SAME: now", Options()); EXPECT_THAT(parsed.first.status(), Eq(Status::BadRule)); EXPECT_THAT(parsed.first.message(), HasSubstr("CHECK-SAME can't be the first check rule")); EXPECT_THAT(parsed.second, Eq(CheckList({}))); } TEST(ParseChecks, CheckSameCantBeFirstDifferentPrefix) { const auto parsed = ParseChecks("BOO-SAME: now", Options().SetPrefix("BOO")); EXPECT_THAT(parsed.first.status(), Eq(Status::BadRule)); EXPECT_THAT(parsed.first.message(), HasSubstr("BOO-SAME can't be the first check rule")); EXPECT_THAT(parsed.second, Eq(CheckList({}))); } // Check::Matches struct CheckMatchCase { std::string input; Check check; bool expected; std::string remaining; std::string captured; }; using CheckMatchTest = ::testing::TestWithParam; TEST_P(CheckMatchTest, Samples) { StringPiece str = GetParam().input; StringPiece captured; VarMapping vars; const bool matched = GetParam().check.Matches(&str, &captured, &vars); EXPECT_THAT(matched, Eq(GetParam().expected)) << "Failed on input " << GetParam().input; EXPECT_THAT(std::string(str.data(), str.size()), Eq(GetParam().remaining)); EXPECT_THAT(std::string(captured.data(), captured.size()), Eq(GetParam().captured)); EXPECT_TRUE(vars.empty()); } INSTANTIATE_TEST_SUITE_P( Simple, CheckMatchTest, ValuesIn(std::vector{ {"hello", Check(Type::Simple, "hello"), true, "", "hello"}, {"world", Check(Type::Simple, "hello"), false, "world", ""}, {"in hello now", Check(Type::Simple, "hello"), true, " now", "hello"}, {"hello", Check(Type::Same, "hello"), true, "", "hello"}, {"world", Check(Type::Same, "hello"), false, "world", ""}, {"in hello now", Check(Type::Same, "hello"), true, " now", "hello"}, {"hello", Check(Type::Next, "hello"), true, "", "hello"}, {"world", Check(Type::Next, "hello"), false, "world", ""}, {"in hello now", Check(Type::Next, "hello"), true, " now", "hello"}, {"hello", Check(Type::DAG, "hello"), true, "", "hello"}, {"world", Check(Type::DAG, "hello"), false, "world", ""}, {"in hello now", Check(Type::DAG, "hello"), true, " now", "hello"}, {"hello", Check(Type::Label, "hello"), true, "", "hello"}, {"world", Check(Type::Label, "hello"), false, "world", ""}, {"in hello now", Check(Type::Label, "hello"), true, " now", "hello"}, {"hello", Check(Type::Label, "hello"), true, "", "hello"}, {"world", Check(Type::Label, "hello"), false, "world", ""}, {"in hello now", Check(Type::Label, "hello"), true, " now", "hello"}, {"hello", Check(Type::Not, "hello"), true, "", "hello"}, {"world", Check(Type::Not, "hello"), false, "world", ""}, {"in hello now", Check(Type::Not, "hello"), true, " now", "hello"}, })); // Check::Part::Regex TEST(CheckPart, FixedPartRegex) { VarMapping vm; EXPECT_THAT(Part(Part::Type::Fixed, "abc").Regex(vm), Eq("abc")); EXPECT_THAT(Part(Part::Type::Fixed, "a.bc").Regex(vm), Eq("a\\.bc")); EXPECT_THAT(Part(Part::Type::Fixed, "a?bc").Regex(vm), Eq("a\\?bc")); EXPECT_THAT(Part(Part::Type::Fixed, "a+bc").Regex(vm), Eq("a\\+bc")); EXPECT_THAT(Part(Part::Type::Fixed, "a*bc").Regex(vm), Eq("a\\*bc")); EXPECT_THAT(Part(Part::Type::Fixed, "a[b]").Regex(vm), Eq("a\\[b\\]")); EXPECT_THAT(Part(Part::Type::Fixed, "a[-]").Regex(vm), Eq("a\\[\\-\\]")); EXPECT_THAT(Part(Part::Type::Fixed, "a(-)b").Regex(vm), Eq("a\\(\\-\\)b")); } TEST(CheckPart, RegexPartRegex) { VarMapping vm; EXPECT_THAT(Part(Part::Type::Regex, "abc").Regex(vm), Eq("abc")); EXPECT_THAT(Part(Part::Type::Regex, "a.bc").Regex(vm), Eq("a.bc")); EXPECT_THAT(Part(Part::Type::Regex, "a?bc").Regex(vm), Eq("a?bc")); EXPECT_THAT(Part(Part::Type::Regex, "a+bc").Regex(vm), Eq("a+bc")); EXPECT_THAT(Part(Part::Type::Regex, "a*bc").Regex(vm), Eq("a*bc")); EXPECT_THAT(Part(Part::Type::Regex, "a[b]").Regex(vm), Eq("a[b]")); EXPECT_THAT(Part(Part::Type::Regex, "a[-]").Regex(vm), Eq("a[-]")); EXPECT_THAT(Part(Part::Type::Regex, "a(-)b").Regex(vm), Eq("a(-)b")); } } // namespace effcee-2019.1/effcee/cursor.h000066400000000000000000000062661367220107600157440ustar00rootroot00000000000000// Copyright 2017 The Effcee Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef EFFCEE_CURSOR_H #define EFFCEE_CURSOR_H #include #include #include "re2/stringpiece.h" namespace effcee { using StringPiece = re2::StringPiece; // Represents a position in a StringPiece, while tracking line number. class Cursor { public: explicit Cursor(StringPiece str) : remaining_(str), line_num_(1) {} StringPiece remaining() const { return remaining_; } // Returns the current 1-based line number. int line_num() const { return line_num_; } // Returns true if the remaining text is empty. bool Exhausted() const { return remaining_.empty(); } // Returns a string piece from the current position until the end of the line // or the end of input, up to and including the newline. StringPiece RestOfLine() const { const auto newline_pos = remaining_.find('\n'); return remaining_.substr(0, newline_pos + (newline_pos != StringPiece::npos)); } // Advance |n| characters. Does not adjust line count. The next |n| // characters should not contain newlines if line numbering is to remain // up to date. Returns this object. Cursor& Advance(size_t n) { remaining_.remove_prefix(n); return *this; } // Advances the cursor by a line. If no text remains, then does nothing. // Otherwise removes the first line (including newline) and increments the // line count. If there is no newline then the remaining string becomes // empty. Returns this object. Cursor& AdvanceLine() { if (remaining_.size()) { Advance(RestOfLine().size()); ++line_num_; } return *this; } private: // The remaining text, after all previous advancements. References the // original string storage. StringPiece remaining_; // The current 1-based line number. int line_num_; }; // Returns string containing a description of the line containing a given // subtext, with a message, and a caret displaying the subtext position. // Assumes subtext does not contain a newline. inline std::string LineMessage(StringPiece text, StringPiece subtext, StringPiece message) { Cursor c(text); StringPiece full_line = c.RestOfLine(); while (subtext.end() - full_line.end() > 0) { c.AdvanceLine(); full_line = c.RestOfLine(); } const char* full_line_newline = full_line.find('\n') == StringPiece::npos ? "\n" : ""; const size_t column = subtext.begin() - full_line.begin(); std::ostringstream out; out << ":" << c.line_num() << ":" << (1 + column) << ": " << message << "\n" << full_line << full_line_newline << std::string(column, ' ') << "^\n"; return out.str(); } } // namespace effcee #endif effcee-2019.1/effcee/cursor_test.cc000066400000000000000000000122731367220107600171340ustar00rootroot00000000000000// Copyright 2017 The Effcee Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "gmock/gmock.h" #include "cursor.h" namespace { using effcee::Cursor; using effcee::LineMessage; using effcee::StringPiece; using ::testing::Eq; using ::testing::HasSubstr; // text method // remaining and Advance methods TEST(Cursor, AdvanceReturnsTheCursorItself) { Cursor c("foo"); EXPECT_THAT(&c.Advance(1), Eq(&c)); } TEST(Cursor, RemainingBeginsEqualToText) { const char* original = "The Smiths"; Cursor c(original); EXPECT_THAT(c.remaining().begin(), Eq(original)); } TEST(Cursor, RemainingDiminishesByPreviousAdvanceCalls) { const char* original = "The Smiths are a great 80s band"; Cursor c(original); c.Advance(4); EXPECT_THAT(c.remaining(), Eq("Smiths are a great 80s band")); EXPECT_THAT(c.remaining().begin(), Eq(original + 4)); c.Advance(11); EXPECT_THAT(c.remaining(), Eq("a great 80s band")); EXPECT_THAT(c.remaining().begin(), Eq(original + 15)); c.Advance(c.remaining().size()); EXPECT_THAT(c.remaining(), Eq("")); EXPECT_THAT(c.remaining().begin(), Eq(original + 31)); } // Exhausted method TEST(Cursor, ExhaustedImmediatelyWhenStartingWithEmptyString) { Cursor c(""); EXPECT_TRUE(c.Exhausted()); } TEST(Cursor, ExhaustedWhenRemainingIsEmpty) { Cursor c("boo"); EXPECT_FALSE(c.Exhausted()); c.Advance(2); EXPECT_FALSE(c.Exhausted()); c.Advance(1); EXPECT_TRUE(c.Exhausted()); } // RestOfLine method TEST(Cursor, RestOfLineOnEmptyReturnsEmpty) { const char* original = ""; Cursor c(original); EXPECT_THAT(c.RestOfLine(), Eq("")); EXPECT_THAT(c.RestOfLine().begin(), Eq(original)); } TEST(Cursor, RestOfLineWithoutNewline) { Cursor c("The end"); EXPECT_THAT(c.RestOfLine(), Eq("The end")); } TEST(Cursor, RestOfLineGetsLineUpToAndIncludingNewline) { Cursor c("The end\nOf an era"); EXPECT_THAT(c.RestOfLine(), Eq("The end\n")); } TEST(Cursor, RestOfLineGetsOnlyFromRemainingText) { Cursor c("The end\nOf an era"); c.Advance(4); EXPECT_THAT(c.remaining(), Eq("end\nOf an era")); EXPECT_THAT(c.RestOfLine(), Eq("end\n")); } // AdvanceLine and line_num methods TEST(Cursor, AdvanceLineReturnsTheCursorItself) { Cursor c("foo\nbar"); EXPECT_THAT(&c.AdvanceLine(), Eq(&c)); } TEST(Cursor, AdvanceLineWalksThroughTextByLineAndCountsLines) { const char* original = "The end\nOf an era\nIs here"; Cursor c(original); EXPECT_THAT(c.line_num(), Eq(1)); c.AdvanceLine(); EXPECT_THAT(c.line_num(), Eq(2)); EXPECT_THAT(c.remaining(), Eq("Of an era\nIs here")); EXPECT_THAT(c.remaining().begin(), Eq(original + 8)); c.AdvanceLine(); EXPECT_THAT(c.line_num(), Eq(3)); EXPECT_THAT(c.remaining(), Eq("Is here")); EXPECT_THAT(c.remaining().begin(), Eq(original + 18)); c.AdvanceLine(); EXPECT_THAT(c.line_num(), Eq(4)); EXPECT_THAT(c.remaining(), Eq("")); EXPECT_THAT(c.remaining().begin(), Eq(original + 25)); } TEST(Cursor, AdvanceLineIsNoopAfterEndIsReached) { Cursor c("One\nTwo"); c.AdvanceLine(); EXPECT_THAT(c.line_num(), Eq(2)); EXPECT_THAT(c.remaining(), Eq("Two")); c.AdvanceLine(); EXPECT_THAT(c.line_num(), Eq(3)); EXPECT_THAT(c.remaining(), Eq("")); c.AdvanceLine(); EXPECT_THAT(c.line_num(), Eq(3)); EXPECT_THAT(c.remaining(), Eq("")); } // LineMessage free function. TEST(LineMessage, SubtextIsFirst) { StringPiece text("Foo\nBar"); StringPiece subtext(text.begin(), 3); EXPECT_THAT(LineMessage(text, subtext, "loves quiche"), Eq(":1:1: loves quiche\nFoo\n^\n")); } TEST(LineMessage, SubtextDoesNotEndInNewline) { StringPiece text("Foo\nBar"); StringPiece subtext(text.begin()+4, 3); EXPECT_THAT(LineMessage(text, subtext, "loves quiche"), Eq(":2:1: loves quiche\nBar\n^\n")); } TEST(LineMessage, SubtextPartwayThroughItsLine) { StringPiece text("Food Life\nBar"); StringPiece subtext(text.begin() + 5, 3); // "Lif" EXPECT_THAT(LineMessage(text, subtext, "loves quiche"), Eq(":1:6: loves quiche\nFood Life\n ^\n")); } TEST(LineMessage, SubtextOnSubsequentLine) { StringPiece text("Food Life\nBar Fight\n"); StringPiece subtext(text.begin() + 14, 5); // "Fight" EXPECT_THAT(LineMessage(text, subtext, "loves quiche"), Eq(":2:5: loves quiche\nBar Fight\n ^\n")); } TEST(LineMessage, SubtextIsEmptyAndInMiddle) { StringPiece text("Food"); StringPiece subtext(text.begin() + 2, 0); EXPECT_THAT(LineMessage(text, subtext, "loves quiche"), Eq(":1:3: loves quiche\nFood\n ^\n")); } TEST(LineMessage, SubtextIsEmptyAndAtVeryEnd) { StringPiece text("Food"); StringPiece subtext(text.begin() + 4, 0); EXPECT_THAT(LineMessage(text, subtext, "loves quiche"), Eq(":1:5: loves quiche\nFood\n ^\n")); } } // namespace effcee-2019.1/effcee/diagnostic.h000066400000000000000000000036571367220107600165540ustar00rootroot00000000000000// Copyright 2017 The Effcee Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef EFFCEE_DIAGNOSTIC_H #define EFFCEE_DIAGNOSTIC_H #include #include "effcee/effcee.h" namespace effcee { // A Diagnostic contains a Result::Status value and can accumulate message // values via operator<<. It is convertible to a Result object containing the // status and the stringified message. class Diagnostic { public: explicit Diagnostic(Result::Status status) : status_(status) {} // Copy constructor. Diagnostic(const Diagnostic& other) : status_(other.status_), message_() { // We can't move an ostringstream. As a fallback, we'd like to use the // std::ostringstream(std::string init_string) constructor. However, that // initial string disappears inexplicably the first time we shift onto // the |message_| member. So fall back further and use the default // constructor and later use an explicit shift. message_ << other.message_.str(); } // Appends the given value to the accumulated message. template Diagnostic& operator<<(const T& value) { message_ << value; return *this; } // Converts this object to a result value containing the stored status and a // stringified copy of the message. operator Result() const { return Result(status_, message_.str()); } private: Result::Status status_; std::ostringstream message_; }; } // namespace effcee #endif effcee-2019.1/effcee/diagnostic_test.cc000066400000000000000000000036461367220107600177470ustar00rootroot00000000000000// Copyright 2017 The Effcee Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "gmock/gmock.h" #include "diagnostic.h" namespace { using effcee::Diagnostic; using effcee::Result; using testing::Eq; using Status = effcee::Result::Status; // Check conversion preserves status. TEST(Diagnostic, ConvertsToResultWithSameOkStatus) { const Diagnostic d(Status::Ok); const Result r(d); EXPECT_THAT(r.status(), Eq(Status::Ok)); } TEST(Diagnostic, ConvertsToResultWithSameFailStatus) { const Diagnostic d(Status::Fail); const Result r(d); EXPECT_THAT(r.status(), Eq(Status::Fail)); } // Check conversion, with messages. TEST(Diagnostic, MessageDefaultsToEmpty) { const Diagnostic d(Status::Ok); const Result r(d); EXPECT_THAT(r.message(), Eq("")); } TEST(Diagnostic, MessageAccumulatesValuesOfDifferentTypes) { Diagnostic d(Status::Ok); d << "hello" << ' ' << 42 << " and " << 32u << " and " << 1.25; const Result r(d); EXPECT_THAT(r.message(), Eq("hello 42 and 32 and 1.25")); } // Check copying TEST(Diagnostic, CopyRetainsOriginalMessage) { Diagnostic d(Status::Ok); d << "hello"; Diagnostic d2 = d; const Result r(d2); EXPECT_THAT(r.message(), Eq("hello")); } TEST(Diagnostic, ShiftOnCopyAppendsToOriginalMessage) { Diagnostic d(Status::Ok); d << "hello"; Diagnostic d2 = d; d2 << " world"; const Result r(d2); EXPECT_THAT(r.message(), Eq("hello world")); } } // namespace effcee-2019.1/effcee/effcee.h000066400000000000000000000065601367220107600156410ustar00rootroot00000000000000// Copyright 2017 The Effcee Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef EFFCEE_EFFCEE_H #define EFFCEE_EFFCEE_H #include #include "re2/re2.h" namespace effcee { // TODO(dneto): Provide a check language tutorial / manual. // This does not implement the equivalents of FileCheck options: // --match-full-lines // --strict-whitespace // --implicit-ch3eck-not // --enable-var-scope using StringPiece = re2::StringPiece; // Options for matching. class Options { public: Options() : prefix_("CHECK"), input_name_(""), checks_name_("") {} // Sets rule prefix to a copy of |prefix|. Returns this object. Options& SetPrefix(StringPiece prefix) { prefix_ = std::string(prefix.begin(), prefix.end()); return *this; } const std::string& prefix() const { return prefix_; } // Sets the input name. Returns this object. // Use this for file names, for example. Options& SetInputName(StringPiece name) { input_name_ = std::string(name.begin(), name.end()); return *this; } const std::string& input_name() const { return input_name_; } // Sets the checks input name. Returns this object. // Use this for file names, for example. Options& SetChecksName(StringPiece name) { checks_name_ = std::string(name.begin(), name.end()); return *this; } const std::string& checks_name() const { return checks_name_; } private: std::string prefix_; std::string input_name_; std::string checks_name_; }; // The result of an attempted match. class Result { public: enum class Status { Ok = 0, Fail, // A failure to match BadOption, // A bad option was specified NoRules, // No rules were specified BadRule, // A bad rule was specified }; // Constructs a result with a given status. explicit Result(Status status) : status_(status) {} // Constructs a result with the given message. Keeps a copy of the message. Result(Status status, StringPiece message) : status_(status), message_({message.begin(), message.end()}) {} Status status() const { return status_; } // Returns true if the match was successful. operator bool() const { return status_ == Status::Ok; } const std::string& message() const { return message_; } // Sets the error message to a copy of |message|. Returns this object. Result& SetMessage(StringPiece message) { message_ = std::string(message.begin(), message.end()); return *this; } private: // Status code indicating success, or kind of failure. Status status_; // Message describing the failure, if any. On success, this is empty. std::string message_; }; // Returns the result of attempting to match |text| against the pattern // program in |checks|, with the given |options|. Result Match(StringPiece text, StringPiece checks, const Options& options = Options()); } // namespace effcee #endif effcee-2019.1/effcee/make_unique.h000066400000000000000000000020351367220107600167200ustar00rootroot00000000000000// Copyright 2017 The Effcee Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef EFFCEE_MAKE_UNIQUE_H #define EFFCEE_MAKE_UNIQUE_H #include namespace effcee { // Constructs an object of type T and wraps it in a std::unique_ptr. // This functionality comes with C++14. The following is the standard // recipe for use with C++11. template std::unique_ptr make_unique(Args&&... args) { return std::unique_ptr(new T(std::forward(args)...)); } } // namespace effcee #endif effcee-2019.1/effcee/match.cc000066400000000000000000000251221367220107600156510ustar00rootroot00000000000000// Copyright 2017 The Effcee Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include "check.h" #include "cursor.h" #include "diagnostic.h" #include "effcee.h" #include "to_string.h" using effcee::Check; using Status = effcee::Result::Status; using Type = effcee::Check::Type; namespace effcee { Result Match(StringPiece input, StringPiece checks, const Options& options) { const auto& parse_result = ParseChecks(checks, options); if (!parse_result.first) return parse_result.first; // A mapping from variable names to values. This is updated when a check rule // matches a variable definition. VarMapping vars; // We think of the input string as a sequence of lines that can satisfy // the checks. Walk through the rules until no unsatisfied checks are left. // We will erase a check when it has been satisifed. const CheckList& pattern = parse_result.second; assert(pattern.size() > 0); // What checks are resolved? Entry |i| is true when check |i| in the // pattern is resolved. std::vector resolved(pattern.size(), false); // The matching algorithm scans both the input and the pattern from start // to finish. At the start, all checks are unresolved. We try to match // each line in the input against the unresolved checks in a sliding window // in the pattern. When a positive check matches, we mark it as resolved. // When a negative check matches, the algorithm terminates with failure. // We mark a negative check as resolved when it is the earliest unresolved // check and the first positive check after it is resolved. // Initially the pattern window is just the first element. // |first_check| is the first unresolved check. size_t first_check = 0; const size_t num_checks = pattern.size(); // The 1-based line number of the most recent successful match. int matched_line_num = 0; // Set up a cursor to scan the input, and helpers for generating diagnostics. Cursor cursor(input); // Points to the end of the previous positive match. StringPiece previous_match_end = input.substr(0, 0); // Returns a failure diagnostic without a message.; auto fail = []() { return Diagnostic(Status::Fail); }; // Returns a string describing the filename, line, and column of a check rule, // including the text of the check rule and a caret pointing to the parameter // string. auto check_msg = [&checks, &options](StringPiece where, StringPiece message) { std::ostringstream out; out << options.checks_name() << LineMessage(checks, where, message); return out.str(); }; // Returns a string describing the filename, line, and column of an input // string position, including the full line containing the position, and a // caret pointing to the position. auto input_msg = [&input, &options](StringPiece where, StringPiece message) { std::ostringstream out; out << options.input_name() << LineMessage(input, where, message); return out.str(); }; // Returns a string describing the value of each variable use in the // given check, in the context of the |where| portion of the input line. auto var_notes = [&input_msg, &vars](StringPiece where, const Check& check) { std::ostringstream out; for (const auto& part : check.parts()) { const auto var_use = part->VarUseName(); if (!var_use.empty()) { std::ostringstream phrase; std::string var_use_str(ToString(var_use)); if (vars.find(var_use_str) != vars.end()) { phrase << "note: with variable \"" << var_use << "\" equal to \"" << vars[var_use_str] << "\""; } else { phrase << "note: uses undefined variable \"" << var_use << "\""; } out << input_msg(where, phrase.str()); } } return out.str(); }; // For each line. for (; !cursor.Exhausted(); cursor.AdvanceLine()) { // Try to match the current line against the unresolved checks. // The number of characters the cursor should advance to accommodate a // recent DAG check match. size_t deferred_advance = 0; bool scan_this_line = true; while (scan_this_line) { // Skip the initial segment of resolved checks. Slides the left end of // the pattern window toward the right. while (first_check < num_checks && resolved[first_check]) ++first_check; // We've reached the end of the pattern. Declare success. if (first_check == num_checks) return Result(Result::Status::Ok); size_t first_unresolved_dag = num_checks; size_t first_unresolved_negative = num_checks; bool resolved_something = false; for (size_t i = first_check; i < num_checks; ++i) { if (resolved[i]) continue; const Check& check = pattern[i]; if (check.type() != Type::DAG) { cursor.Advance(deferred_advance); deferred_advance = 0; } const StringPiece rest_of_line = cursor.RestOfLine(); StringPiece unconsumed = rest_of_line; StringPiece captured; if (check.Matches(&unconsumed, &captured, &vars)) { if (check.type() == Type::Not) { return fail() << input_msg(captured, "error: CHECK-NOT: string occurred!") << check_msg( check.param(), "note: CHECK-NOT: pattern specified here") << var_notes(captured, check); } if (check.type() == Type::Same && cursor.line_num() != matched_line_num) { return fail() << check_msg(check.param(), "error: CHECK-SAME: is not on the same line as " "previous match") << input_msg(captured, "note: 'next' match was here") << input_msg(previous_match_end, "note: previous match ended here"); } if (check.type() == Type::Next) { if (cursor.line_num() == matched_line_num) { return fail() << check_msg(check.param(), "error: CHECK-NEXT: is on the same line as " "previous match") << input_msg(captured, "note: 'next' match was here") << input_msg(previous_match_end, "note: previous match ended here") << var_notes(previous_match_end, check); } if (cursor.line_num() > 1 + matched_line_num) { // This must be valid since there was an intervening line. const auto non_match = Cursor(input) .Advance(previous_match_end.begin() - input.begin()) .AdvanceLine() .RestOfLine(); return fail() << check_msg(check.param(), "error: CHECK-NEXT: is not on the line after " "the previous match") << input_msg(captured, "note: 'next' match was here") << input_msg(previous_match_end, "note: previous match ended here") << input_msg(non_match, "note: non-matching line after previous " "match is here") << var_notes(previous_match_end, check); } } if (check.type() != Type::DAG && first_unresolved_dag < i) { return fail() << check_msg(pattern[first_unresolved_dag].param(), "error: expected string not found in input") << input_msg(previous_match_end, "note: scanning from here") << input_msg(captured, "note: next check matches here") << var_notes(previous_match_end, check); } resolved[i] = true; matched_line_num = cursor.line_num(); previous_match_end = unconsumed; resolved_something = true; // Resolve any prior negative checks that precede an unresolved DAG. for (auto j = first_unresolved_negative, limit = std::min(first_unresolved_dag, i); j < limit; ++j) { resolved[j] = true; } // Normally advance past the matched text. But DAG checks might need // to match out of order on the same line. So only advance for // non-DAG cases. const size_t advance_proposal = rest_of_line.size() - unconsumed.size(); if (check.type() == Type::DAG) { deferred_advance = std::max(deferred_advance, advance_proposal); } else { cursor.Advance(advance_proposal); } } else { // This line did not match the check. if (check.type() == Type::Not) { first_unresolved_negative = std::min(first_unresolved_negative, i); // An unresolved Not check stops the search for more DAG checks. if (first_unresolved_dag < num_checks) i = num_checks; } else if (check.type() == Type::DAG) { first_unresolved_dag = std::min(first_unresolved_dag, i); } else { // An unresolved non-DAG check check stops this pass over the // checks. i = num_checks; } } } scan_this_line = resolved_something; } } // Fail if there are any unresolved positive checks. for (auto i = first_check; i < num_checks; ++i) { if (resolved[i]) continue; const auto check = pattern[i]; if (check.type() == Type::Not) continue; return fail() << check_msg(check.param(), "error: expected string not found in input") << input_msg(previous_match_end, "note: scanning from here") << var_notes(previous_match_end, check); } return Result(Result::Status::Ok); } } // namespace effcee effcee-2019.1/effcee/match_test.cc000066400000000000000000000700731367220107600167150ustar00rootroot00000000000000// Copyright 2017 The Effcee Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "gmock/gmock.h" #include "effcee.h" namespace { using effcee::Match; using effcee::Options; using ::testing::Eq; using ::testing::HasSubstr; const char* kNotFound = "error: expected string not found in input"; const char* kMissedSame = "error: CHECK-SAME: is not on the same line as previous match"; const char* kNextOnSame = "error: CHECK-NEXT: is on the same line as previous match"; const char* kNextTooLate = "error: CHECK-NEXT: is not on the line after the previous match"; const char* kNotStrFound = "error: CHECK-NOT: string occurred!"; // Match free function TEST(Match, FreeFunctionLinks) { Match("", ""); Match("", "", effcee::Options()); } // Simple checks TEST(Match, OneSimpleCheckPass) { const auto result = Match("Hello", "CHECK: Hello"); EXPECT_TRUE(result) << result.message(); } TEST(Match, OneSimpleCheckFail) { const auto result = Match("World", "CHECK: Hello"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kNotFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK: Hello")); } TEST(Match, TwoSimpleChecksPass) { const auto result = Match("Hello\nWorld", "CHECK: Hello\nCHECK: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, RepeatedCheckFails) { const auto result = Match("Hello\nWorld", "CHECK: Hello\nCHECK: Hello"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kNotFound)); } TEST(Match, TwoSimpleChecksPassWithSurroundingText) { const auto input = R"(Say Hello World Today)"; const auto result = Match(input, "CHECK: Hello\nCHECK: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, TwoSimpleChecksPassWithInterveningText) { const auto input = R"(Hello Between World)"; const auto result = Match(input, "CHECK: Hello\nCHECK: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, TwoSimpleChecksPassWhenInSequenceSameLine) { const auto result = Match("HelloWorld", "CHECK: Hello\nCHECK: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, TwoSimpleChecksFailWhenReversed) { const auto result = Match("HelloWorld", "CHECK: World\nCHECK: Hello"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kNotFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK: Hello")); } TEST(Match, SimpleThenSamePasses) { const auto result = Match("HelloWorld", "CHECK: Hello\nCHECK-SAME: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, SimpleThenSamePassesWithInterveningOnSameLine) { const auto result = Match("Hello...World", "CHECK: Hello\nCHECK-SAME: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, SimpleThenSameFailsIfOnNextLine) { const auto result = Match("Hello\nWorld", "CHECK: Hello\nCHECK-SAME: World"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(),HasSubstr(kMissedSame)); EXPECT_THAT(result.message(), HasSubstr("CHECK-SAME: World")); } TEST(Match, SimpleThenSameFailsIfOnMuchLaterLine) { const auto result = Match("Hello\n\nz\n\nWorld", "CHECK: Hello\nCHECK-SAME: World"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kMissedSame)); EXPECT_THAT(result.message(), HasSubstr("CHECK-SAME: World")); } TEST(Match, SimpleThenSameFailsIfNeverMatched) { const auto result = Match("Hello\nHome", "CHECK: Hello\nCHECK-SAME: World"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kNotFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK-SAME: World")); } TEST(Match, SimpleThenNextOnSameLineFails) { const auto result = Match("HelloWorld", "CHECK: Hello\nCHECK-NEXT: World"); EXPECT_FALSE(result); EXPECT_THAT(result.message(), HasSubstr(kNextOnSame)); EXPECT_THAT(result.message(), HasSubstr("CHECK-NEXT: World")); } TEST(Match, SimpleThenNextPassesIfOnNextLine) { const auto result = Match("Hello\nWorld", "CHECK: Hello\nCHECK-NEXT: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, SimpleThenNextFailsIfOnAfterNextLine) { const auto result = Match("Hello\nfoo\nWorld", "CHECK: Hello\nCHECK-NEXT: World"); EXPECT_THAT(result.message(), HasSubstr(kNextTooLate)); EXPECT_THAT(result.message(), HasSubstr("CHECK-NEXT: World")); } TEST(Match, SimpleThenNextFailsIfNeverMatched) { const auto result = Match("Hello\nHome", "CHECK: Hello\nCHECK-NEXT: World"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kNotFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK-NEXT: World")); } // TODO: CHECK-NOT TEST(Match, AloneNotNeverSeenPasses) { const auto result = Match("Hello", "CHECK-NOT: Borg"); EXPECT_TRUE(result); } TEST(Match, LeadingNotNeverSeenPasses) { const auto result = Match("Hello", "CHECK-NOT: Borg\nCHECK: Hello"); EXPECT_TRUE(result); } TEST(Match, BetweenNotNeverSeenPasses) { const auto result = Match("HelloWorld", "CHECK: Hello\nCHECK-NOT: Borg\nCHECK: World"); EXPECT_TRUE(result); } TEST(Match, BetweenNotDotsNeverSeenPasses) { // The before and after matches occur on the same line. const auto result = Match("Hello...World", "CHECK: Hello\nCHECK-NOT: Borg\nCHECK: World"); EXPECT_TRUE(result); } TEST(Match, BetweenNotLinesNeverSeenPasses) { // The before and after matches occur on different lines. const auto result = Match("Hello\nz\nWorld", "CHECK: Hello\nCHECK-NOT: Borg\nCHECK: World"); EXPECT_TRUE(result); } TEST(Match, NotBetweenMatchesPasses) { const auto result = Match("Hello\nWorld\nBorg\n", "CHECK: Hello\nCHECK-NOT: Borg\nCHECK: World"); EXPECT_TRUE(result); } TEST(Match, NotBeforeFirstMatchPasses) { const auto result = Match("Hello\nWorld\nBorg\n", "CHECK-NOT: World\nCHECK: Hello"); EXPECT_TRUE(result); } TEST(Match, NotAfterLastMatchPasses) { const auto result = Match("Hello\nWorld\nBorg\n", "CHECK: World\nCHECK-NOT: Hello"); EXPECT_TRUE(result); } TEST(Match, NotBeforeFirstMatchFails) { const auto result = Match("Hello\nWorld\n", "CHECK-NOT: Hello\nCHECK: World"); EXPECT_FALSE(result); } TEST(Match, NotBetweenMatchesFails) { const auto result = Match("Hello\nWorld\nBorg\n", "CHECK: Hello\nCHECK-NOT: World\nCHECK: Borg"); EXPECT_FALSE(result); } TEST(Match, NotAfterLastMatchFails) { const auto result = Match("Hello\nWorld\n", "CHECK: Hello\nCHECK-NOT: World"); EXPECT_FALSE(result); } TEST(Match, TrailingNotNeverSeenPasses) { const auto result = Match("Hello", "CHECK: Hello\nCHECK-NOT: Borg"); EXPECT_TRUE(result); } TEST(Match, AloneNotSeenFails) { const auto result = Match("Borg", "CHECK-NOT: Borg"); EXPECT_FALSE(result); EXPECT_THAT(result.message(), HasSubstr(kNotStrFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK-NOT: Borg")); } TEST(Match, LeadingNotSeenFails) { const auto result = Match("Borg", "CHECK-NOT: Borg\nCHECK: Hello"); EXPECT_FALSE(result); EXPECT_THAT(result.message(), HasSubstr(kNotStrFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK-NOT: Borg")); } TEST(Match, BetweenNotSeenFails) { const auto result = Match("HelloBorgWorld", "CHECK: Hello\nCHECK-NOT: Borg\nCHECK: World"); EXPECT_FALSE(result); EXPECT_THAT(result.message(), HasSubstr(kNotStrFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK-NOT: Borg")); } TEST(Match, BetweenNotDotsSeenFails) { const auto result = Match("Hello.Borg.World", "CHECK: Hello\nCHECK-NOT: Borg\nCHECK: World"); EXPECT_FALSE(result); EXPECT_THAT(result.message(), HasSubstr(kNotStrFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK-NOT: Borg")); } TEST(Match, BetweenNotLinesSeenFails) { const auto result = Match("Hello\nBorg\nWorld", "CHECK: Hello\nCHECK-NOT: Borg\nCHECK: World"); EXPECT_FALSE(result); EXPECT_THAT(result.message(), HasSubstr(kNotStrFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK-NOT: Borg")); } TEST(Match, TrailingNotSeenFails) { const auto result = Match("HelloBorg", "CHECK: Hello\nCHECK-NOT: Borg"); EXPECT_FALSE(result); EXPECT_THAT(result.message(), HasSubstr(kNotStrFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK-NOT: Borg")); } // WIP: CHECK-LABEL TEST(Match, OneLabelCheckPass) { const auto result = Match("Hello", "CHECK-LABEL: Hello"); EXPECT_TRUE(result) << result.message(); } TEST(Match, OneLabelCheckFail) { const auto result = Match("World", "CHECK-LABEL: Hello"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kNotFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK-LABEL: Hello")); } TEST(Match, TwoLabelChecksPass) { const auto result = Match("Hello\nWorld", "CHECK-LABEL: Hello\nCHECK-LABEL: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, TwoLabelChecksPassWithSurroundingText) { const auto input = R"(Say Hello World Today)"; const auto result = Match(input, "CHECK-LABEL: Hello\nCHECK-LABEL: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, TwoLabelChecksPassWithInterveningText) { const auto input = R"(Hello Between World)"; const auto result = Match(input, "CHECK-LABEL: Hello\nCHECK-LABEL: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, TwoLabelChecksPassWhenInSequenceSameLine) { const auto result = Match("HelloWorld", "CHECK-LABEL: Hello\nCHECK-LABEL: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, TwoLabelChecksFailWhenReversed) { const auto result = Match("HelloWorld", "CHECK-LABEL: World\nCHECK-LABEL: Hello"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kNotFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK-LABEL: Hello")); } // WIP: Mixture of Simple and Label checks TEST(Match, SimpleAndLabelChecksPass) { const auto result = Match("Hello\nWorld", "CHECK: Hello\nCHECK-LABEL: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, LabelAndSimpleChecksPass) { const auto result = Match("Hello\nWorld", "CHECK-LABEL: Hello\nCHECK: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, SimpleAndLabelChecksFails) { const auto result = Match("Hello\nWorld", "CHECK: Hello\nCHECK-LABEL: Band"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kNotFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK-LABEL: Band")); } TEST(Match, LabelAndSimpleChecksFails) { const auto result = Match("Hello\nWorld", "CHECK-LABEL: Hello\nCHECK: Band"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kNotFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK: Band")); } // DAG checks: Part 1: Tests simlar to simple checks tests TEST(Match, OneDAGCheckPass) { const auto result = Match("Hello", "CHECK-DAG: Hello"); EXPECT_TRUE(result) << result.message(); } TEST(Match, OneDAGCheckFail) { const auto result = Match("World", "CHECK-DAG: Hello"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kNotFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK-DAG: Hello")); } TEST(Match, TwoDAGChecksPass) { const auto result = Match("Hello\nWorld", "CHECK-DAG: Hello\nCHECK-DAG: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, TwoDAGChecksPassWithSurroundingText) { const auto input = R"(Say Hello World Today)"; const auto result = Match(input, "CHECK-DAG: Hello\nCHECK-DAG: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, TwoDAGChecksPassWithInterveningText) { const auto input = R"(Hello Between World)"; const auto result = Match(input, "CHECK-DAG: Hello\nCHECK-DAG: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, TwoDAGChecksPassWhenInSequenceSameLine) { const auto result = Match("HelloWorld", "CHECK-DAG: Hello\nCHECK-DAG: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, DAGThenSamePasses) { const auto result = Match("HelloWorld", "CHECK-DAG: Hello\nCHECK-SAME: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, DAGThenSamePassesWithInterveningOnSameLine) { const auto result = Match("Hello...World", "CHECK-DAG: Hello\nCHECK-SAME: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, DAGThenSameFailsIfOnNextLine) { const auto result = Match("Hello\nWorld", "CHECK-DAG: Hello\nCHECK-SAME: World"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kMissedSame)); EXPECT_THAT(result.message(), HasSubstr("CHECK-SAME: World")); } TEST(Match, DAGThenSameFailsIfOnMuchLaterLine) { const auto result = Match("Hello\n\nz\n\nWorld", "CHECK-DAG: Hello\nCHECK-SAME: World"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kMissedSame)); EXPECT_THAT(result.message(), HasSubstr("CHECK-SAME: World")); } TEST(Match, DAGThenSameFailsIfNeverMatched) { const auto result = Match("Hello\nHome", "CHECK-DAG: Hello\nCHECK-SAME: World"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kNotFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK-SAME: World")); } TEST(Match, DAGThenNextOnSameLineFails) { const auto result = Match("HelloWorld", "CHECK-DAG: Hello\nCHECK-NEXT: World"); EXPECT_FALSE(result); EXPECT_THAT(result.message(), HasSubstr(kNextOnSame)); EXPECT_THAT(result.message(), HasSubstr("CHECK-NEXT: World")); } TEST(Match, DAGThenNextPassesIfOnNextLine) { const auto result = Match("Hello\nWorld", "CHECK-DAG: Hello\nCHECK-NEXT: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, DAGThenNextPassesIfOnAfterNextLine) { const auto result = Match("Hello\nWorld", "CHECK-DAG: Hello\nCHECK-NEXT: World"); EXPECT_TRUE(result) << result.message(); } TEST(Match, DAGThenNextFailsIfNeverMatched) { const auto result = Match("Hello\nHome", "CHECK-DAG: Hello\nCHECK-NEXT: World"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kNotFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK-NEXT: World")); } // DAG checks: Part 2: Out of order matching TEST(Match, TwoDAGMatchedOutOfOrderPasses) { const auto result = Match("Hello\nWorld", "CHECK-DAG: World\nCHECK-DAG: Hello"); EXPECT_TRUE(result) << result.message(); } TEST(Match, ThreeDAGMatchedOutOfOrderPasses) { const auto result = Match("Hello\nWorld\nNow", "CHECK-DAG: Now\nCHECK-DAG: World\nCHECK-DAG: Hello"); EXPECT_TRUE(result) << result.message(); } TEST(Match, TwoDAGChecksPassWhenReversedMatchingSameLine) { const auto result = Match("HelloWorld", "CHECK-DAG: World\nCHECK-DAG: Hello"); EXPECT_TRUE(result) << result.message(); } TEST(Match, DAGChecksGreedilyConsumeInput) { const auto result = Match("Hello\nBlocker\nWorld\n", "CHECK-DAG: Hello\nCHECK-DAG: World\nCHECK: Blocker"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kNotFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK-DAG: World")); } // DAG checks: Part 3: Interaction with Not checks TEST(Match, DAGsAreSeparatedByNot) { // In this case the search for "Before" consumes the entire input. const auto result = Match("After\nBlocker\nBefore\n", "CHECK-DAG: Before\nCHECK-NOT: nothing\nCHECK-DAG: After"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kNotFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK-DAG: After")); } TEST(Match, TwoDAGsAreSeparatedByNot) { const auto result = Match("After\nApres\nBlocker\nBefore\nAnte", "CHECK-DAG: Ante\nCHECK-DAG: Before\nCHECK-NOT: " "nothing\nCHECK-DAG: Apres\nCHECK-DAG: After"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kNotFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK-DAG: Apres")); } // DAG checks: Part 4: Interaction with simple checks TEST(Match, DAGsAreTerminatedBySimple) { const auto result = Match("After\nsimple\nBefore\n", "CHECK-DAG: Before\nCHECK: simple\nCHECK-DAG: After"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kNotFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK-DAG: Before")); } TEST(Match, TwoDAGsAreTerminatedBySimple) { const auto result = Match("After\nApres\nBlocker\nBefore\nAnte", "CHECK-DAG: Ante\nCHECK-DAG: Before\nCHECK: " "Blocker\nCHECK-DAG: Apres\nCHECK-DAG: After"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(kNotFound)); EXPECT_THAT(result.message(), HasSubstr("CHECK-DAG: Ante")); } // Test detailed message text TEST(Match, MessageStringNotFoundWhenNeverMatchedAnything) { const char* input = R"(Begin Hello World)"; const char* checks = R"( Hello ; CHECK: Needle )"; const char* expected = R"(chklist:3:13: error: expected string not found in input ; CHECK: Needle ^ myin.txt:1:1: note: scanning from here Begin ^ )"; const auto result = Match(input, checks, Options().SetInputName("myin.txt").SetChecksName("chklist")); EXPECT_FALSE(result); EXPECT_THAT(result.message(), Eq(expected)) << result.message(); } TEST(Match, MessageStringNotFoundAfterInitialMatch) { const char* input = R"(Begin Hello World)"; const char* checks = R"( Hello ; CHECK-LABEL: Hel ; CHECK: Needle )"; const char* expected = R"(chklist:4:13: error: expected string not found in input ; CHECK: Needle ^ myin.txt:2:4: note: scanning from here Hello ^ )"; const auto result = Match(input, checks, Options().SetInputName("myin.txt").SetChecksName("chklist")); EXPECT_FALSE(result); EXPECT_THAT(result.message(), Eq(expected)) << result.message(); } TEST(Match, MessageCheckNotStringFoundAtStart) { const auto result = Match(" Cheese", "CHECK-NOT: Cheese", Options().SetInputName("in").SetChecksName("checks")); EXPECT_FALSE(result); const char* expected = R"(in:1:3: error: CHECK-NOT: string occurred! Cheese ^ checks:1:12: note: CHECK-NOT: pattern specified here CHECK-NOT: Cheese ^ )"; EXPECT_THAT(result.message(), Eq(expected)) << result.message(); } TEST(Match, MessageCheckNotStringFoundAfterInitialMatch) { const auto result = Match("Cream Cheese", "CHECK: Cream\nCHECK-NOT: Cheese", Options().SetInputName("in").SetChecksName("checks")); EXPECT_FALSE(result); const char* expected = R"(in:1:10: error: CHECK-NOT: string occurred! Cream Cheese ^ checks:2:12: note: CHECK-NOT: pattern specified here CHECK-NOT: Cheese ^ )"; EXPECT_THAT(result.message(), Eq(expected)) << result.message(); } TEST(Match, MessageCheckSameFails) { const char* input = R"( Bees Make Delicious Honey )"; const char* checks = R"( CHECK: Make CHECK-SAME: Honey )"; const auto result = Match( input, checks, Options().SetInputName("in").SetChecksName("checks")); EXPECT_FALSE(result); const char* expected = R"(checks:3:13: error: CHECK-SAME: is not on the same line as previous match CHECK-SAME: Honey ^ in:4:11: note: 'next' match was here Delicious Honey ^ in:3:5: note: previous match ended here Make ^ )"; EXPECT_THAT(result.message(), Eq(expected)) << result.message(); } TEST(Match, MessageCheckNextFailsSinceOnSameLine) { const char* input = R"( Bees Make Delicious Honey )"; const char* checks = R"( CHECK: Bees CHECK-NEXT: Honey )"; const auto result = Match( input, checks, Options().SetInputName("in").SetChecksName("checks")); EXPECT_FALSE(result); const char* expected = R"(checks:3:13: error: CHECK-NEXT: is not on the line after the previous match CHECK-NEXT: Honey ^ in:4:11: note: 'next' match was here Delicious Honey ^ in:2:5: note: previous match ended here Bees ^ in:3:1: note: non-matching line after previous match is here Make ^ )"; EXPECT_THAT(result.message(), Eq(expected)) << result.message(); } TEST(Match, MessageCheckNextFailsSinceLaterLine) { const char* input = R"( Bees Make Delicious Honey )"; const char* checks = R"( CHECK: Make CHECK-NEXT: Honey )"; const auto result = Match( input, checks, Options().SetInputName("in").SetChecksName("checks")); EXPECT_FALSE(result); const char* expected = R"(checks:3:13: error: CHECK-NEXT: is on the same line as previous match CHECK-NEXT: Honey ^ in:2:21: note: 'next' match was here Bees Make Delicious Honey ^ in:2:10: note: previous match ended here Bees Make Delicious Honey ^ )"; EXPECT_THAT(result.message(), Eq(expected)) << result.message(); } TEST(Match, MessageUnresolvedDAG) { const char* input = R"( Bees Make Delicious Honey )"; const char* checks = R"( CHECK: ees CHECK-DAG: Flowers CHECK: Honey )"; const auto result = Match( input, checks, Options().SetInputName("in").SetChecksName("checks")); EXPECT_FALSE(result); const char* expected = R"(checks:3:12: error: expected string not found in input CHECK-DAG: Flowers ^ in:2:5: note: scanning from here Bees ^ in:4:11: note: next check matches here Delicious Honey ^ )"; EXPECT_THAT(result.message(), Eq(expected)) << result.message(); } // Regexp TEST(Match, CheckRegexPass) { const auto result = Match("Hello", "CHECK: He{{ll}}o"); EXPECT_TRUE(result) << result.message(); } TEST(Match, CheckRegexWithFalseStartPass) { // This examples has three false starts. That is, we match the first // few parts of the pattern before we finally match it. const auto result = Match("He Hel Hell Hello Helloo", "CHECK: He{{ll}}oo"); EXPECT_TRUE(result) << result.message(); } TEST(Match, CheckRegexWithRangePass) { const auto result = Match("Hello", "CHECK: He{{[a-z]+}}o"); EXPECT_TRUE(result) << result.message(); } TEST(Match, CheckRegexMatchesEmptyPass) { const auto result = Match("Heo", "CHECK: He{{[a-z]*}}o"); EXPECT_TRUE(result) << result.message(); } TEST(Match, CheckThreeRegexPass) { // This proves that we parsed the check correctly, finding matching pairs // of regexp delimiters {{ and }}. const auto result = Match("Hello World", "CHECK: He{{[a-z]+}}o{{ +}}{{[Ww]}}orld"); EXPECT_TRUE(result) << result.message(); } TEST(Match, CheckRegexFail) { const auto result = Match("Heo", "CHECK: He{{[a-z]*}}o"); EXPECT_TRUE(result) << result.message(); } TEST(Match, MessageStringRegexRegexWithFalseStartFail) { const char* input = "He Hel Hell Hello Hello"; const char* checks = "CHECK: He{{ll}}oo"; const char* expected = R"(chklist:1:8: error: expected string not found in input CHECK: He{{ll}}oo ^ myin.txt:1:1: note: scanning from here He Hel Hell Hello Hello ^ )"; const auto result = Match(input, checks, Options().SetInputName("myin.txt").SetChecksName("chklist")); EXPECT_FALSE(result); EXPECT_THAT(result.message(), Eq(expected)) << result.message(); } TEST(Match, MessageStringRegexNotFoundWhenNeverMatchedAnything) { const char* input = R"(Begin Hello World)"; const char* checks = R"( Hello ; CHECK: He{{[0-9]+}}llo )"; const char* expected = R"(chklist:3:13: error: expected string not found in input ; CHECK: He{{[0-9]+}}llo ^ myin.txt:1:1: note: scanning from here Begin ^ )"; const auto result = Match(input, checks, Options().SetInputName("myin.txt").SetChecksName("chklist")); EXPECT_FALSE(result); EXPECT_THAT(result.message(), Eq(expected)) << result.message(); } // Statefulness: variable definitions and uses TEST(Match, VarDefFollowedByUsePass) { const auto result = Match("Hello\nHello", "CHECK: H[[X:[a-z]+]]o\nCHECK-NEXT: H[[X]]o"); EXPECT_TRUE(result) << result.message(); } TEST(Match, VarDefFollowedByUseFail) { const auto result = Match("Hello\n\nWorld", "CHECK: H[[X:[a-z]+]]o\nCHECK: H[[X]]o"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(":2:8: error: expected string not found in input")); EXPECT_THAT(result.message(), HasSubstr("note: with variable \"X\" equal to \"ell\"")); } TEST(Match, VarDefFollowedByUseFailAfterDAG) { const auto result = Match("Hello\nWorld", "CHECK: H[[X:[a-z]+]]o\nCHECK-DAG: box[[X]]\nCHECK: H[[X]]o"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(":2:12: error: expected string not found in input")); EXPECT_THAT(result.message(), HasSubstr("note: with variable \"X\" equal to \"ell\"")); } TEST(Match, VarDefFollowedByUseInNotCheck) { const auto result = Match("Hello\nHello", "CHECK: H[[X:[a-z]+]]o\nCHECK-NOT: H[[X]]o"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr("CHECK-NOT: string occurred")); EXPECT_THAT(result.message(), HasSubstr("note: with variable \"X\" equal to \"ell\"")); } TEST(Match, VarDefFollowedByUseInNextCheckRightLine) { const auto result = Match("Hello\nHello", "CHECK: H[[X:[a-z]+]]o\nCHECK-NEXT: Blad[[X]]"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(":2:13: error: expected string not found in input")); EXPECT_THAT(result.message(), HasSubstr("note: with variable \"X\" equal to \"ell\"")); } TEST(Match, VarDefFollowedByUseInNextCheckBadLine) { const auto result = Match("Hello\n\nHello", "CHECK: H[[X:[a-z]+]]o\nCHECK-NEXT: H[[X]]o"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr(":2:13: error: CHECK-NEXT: is not on the line after")); EXPECT_THAT(result.message(), HasSubstr("note: with variable \"X\" equal to \"ell\"")); } TEST(Match, UndefinedVarNeverMatches) { const auto result = Match("Hello HeXllo", "CHECK: He[[X]]llo"); EXPECT_FALSE(result) << result.message(); EXPECT_THAT(result.message(), HasSubstr("note: uses undefined variable \"X\"")); } TEST(Match, NoteSeveralUndefinedVariables) { const auto result = Match("Hello HeXllo", "CHECK: He[[X]]l[[YZ]]lo[[Q]]"); EXPECT_FALSE(result) << result.message(); const char* substr = R"( :1:1: note: uses undefined variable "X" Hello HeXllo ^ :1:1: note: uses undefined variable "YZ" Hello HeXllo ^ :1:1: note: uses undefined variable "Q" Hello HeXllo ^ )"; EXPECT_THAT(result.message(), HasSubstr(substr)); } TEST(Match, OutOfOrderDefAndUseViaDAGChecks) { // In this example the X variable should be set to 'l', and then match // the earlier occurrence in 'Hello'. const auto result = Match( "Hello\nWorld", "CHECK-DAG: Wor[[X:[a-z]+]]d\nCHECK-DAG: He[[X]]lo"); EXPECT_FALSE(result) << result.message(); } TEST(Match, VarDefRegexCountsParenthesesProperlyPass) { const auto result = Match( "FirstabababSecondcdcd\n1ababab2cdcd", "CHECK: First[[X:(ab)+]]Second[[Y:(cd)+]]\nCHECK: 1[[X]]2[[Y]]"); EXPECT_TRUE(result) << result.message(); } TEST(Match, VarDefRegexCountsParenthesesProperlyFail) { const auto result = Match("Firstababab1abab", "CHECK: First[[X:(ab)+]]\nCHECK: 1[[X]]"); EXPECT_FALSE(result) << result.message(); const char* substr = R"(:2:8: error: expected string not found in input CHECK: 1[[X]] ^ :1:12: note: scanning from here Firstababab1abab ^ :1:12: note: with variable "X" equal to "ababab" Firstababab1abab ^ )"; EXPECT_THAT(result.message(), HasSubstr(substr)); } } // namespace effcee-2019.1/effcee/options_test.cc000066400000000000000000000070051367220107600173070ustar00rootroot00000000000000// Copyright 2017 The Effcee Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "gmock/gmock.h" #include "effcee.h" namespace { using effcee::Options; using ::testing::Eq; using ::testing::Not; // Options class // Prefix property TEST(Options, DefaultPrefixIsCHECK) { EXPECT_THAT(Options().prefix(), "CHECK"); } TEST(Options, SetPrefixReturnsSelf) { Options options; const Options& other = options.SetPrefix(""); EXPECT_THAT(&other, &options); } TEST(Options, SetPrefixOnceSetsPrefix) { Options options; options.SetPrefix("foo"); EXPECT_THAT(options.prefix(), Eq("foo")); } TEST(Options, SetPrefixCopiesString) { Options options; std::string original("foo"); options.SetPrefix(original); EXPECT_THAT(options.prefix().data(), Not(Eq(original.data()))); } TEST(Options, SetPrefixEmptyStringPossible) { Options options; // This is not useful. options.SetPrefix(""); EXPECT_THAT(options.prefix(), Eq("")); } TEST(Options, SetPrefixTwiceRetainsLastPrefix) { Options options; options.SetPrefix("foo"); options.SetPrefix("bar baz"); EXPECT_THAT(options.prefix(), Eq("bar baz")); } // Input name property TEST(Options, DefaultInputNameIsStdin) { EXPECT_THAT(Options().input_name(), ""); } TEST(Options, SetInputNameReturnsSelf) { Options options; const Options& other = options.SetInputName(""); EXPECT_THAT(&other, &options); } TEST(Options, SetInputNameOnceSetsInputName) { Options options; options.SetInputName("foo"); EXPECT_THAT(options.input_name(), Eq("foo")); } TEST(Options, SetInputNameCopiesString) { Options options; std::string original("foo"); options.SetInputName(original); EXPECT_THAT(options.input_name().data(), Not(Eq(original.data()))); } TEST(Options, SetInputNameEmptyStringPossible) { Options options; options.SetInputName(""); EXPECT_THAT(options.input_name(), Eq("")); } TEST(Options, SetInputNameTwiceRetainsLastInputName) { Options options; options.SetInputName("foo"); options.SetInputName("bar baz"); EXPECT_THAT(options.input_name(), Eq("bar baz")); } // Checks name property TEST(Options, DefaultChecksNameIsStdin) { EXPECT_THAT(Options().checks_name(), ""); } TEST(Options, SetChecksNameReturnsSelf) { Options options; const Options& other = options.SetChecksName(""); EXPECT_THAT(&other, &options); } TEST(Options, SetChecksNameOnceSetsChecksName) { Options options; options.SetChecksName("foo"); EXPECT_THAT(options.checks_name(), Eq("foo")); } TEST(Options, SetChecksNameCopiesString) { Options options; std::string original("foo"); options.SetChecksName(original); EXPECT_THAT(options.checks_name().data(), Not(Eq(original.data()))); } TEST(Options, SetChecksNameEmptyStringPossible) { Options options; options.SetChecksName(""); EXPECT_THAT(options.checks_name(), Eq("")); } TEST(Options, SetChecksNameTwiceRetainsLastChecksName) { Options options; options.SetChecksName("foo"); options.SetChecksName("bar baz"); EXPECT_THAT(options.checks_name(), Eq("bar baz")); } } // namespace effcee-2019.1/effcee/result_test.cc000066400000000000000000000072361367220107600171400ustar00rootroot00000000000000// Copyright 2017 The Effcee Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include "gmock/gmock.h" #include "effcee.h" namespace { using effcee::Result; using ::testing::Combine; using ::testing::Eq; using ::testing::Not; using ::testing::ValuesIn; using Status = effcee::Result::Status; // Result class // Returns a vector of all failure status values. std::vector AllFailStatusValues() { return {Status::NoRules, Status::BadRule}; } // Returns a vector of all status values. std::vector AllStatusValues() { auto result = AllFailStatusValues(); result.push_back(Status::Ok); return result; } // Test one-argument constructor. using ResultStatusTest = ::testing::TestWithParam; TEST_P(ResultStatusTest, ConstructWithAnyStatus) { Result result(GetParam()); EXPECT_THAT(result.status(), Eq(GetParam())); } INSTANTIATE_TEST_SUITE_P(AllStatus, ResultStatusTest, ValuesIn(AllStatusValues())); // Test two-argument constructor. using ResultStatusMessageCase = std::tuple; using ResultStatusMessageTest = ::testing::TestWithParam; TEST_P(ResultStatusMessageTest, ConstructWithStatusAndMessage) { Result result(std::get<0>(GetParam()), std::get<1>(GetParam())); EXPECT_THAT(result.status(), Eq(std::get<0>(GetParam()))); EXPECT_THAT(result.message(), Eq(std::get<1>(GetParam()))); } INSTANTIATE_TEST_SUITE_P(SampleStatusAndMessage, ResultStatusMessageTest, Combine(ValuesIn(AllStatusValues()), ValuesIn(std::vector{ "", "foo bar", "and, how!\n"}))); TEST(ResultConversionTest, OkStatusConvertsToTrue) { Result result(Status::Ok); bool as_bool = result; EXPECT_THAT(as_bool, Eq(true)); } // Test conversion to bool. using ResultFailConversionTest = ::testing::TestWithParam; TEST_P(ResultFailConversionTest, AnyFailStatusConvertsToFalse) { Result result(GetParam()); bool as_bool = result; EXPECT_THAT(as_bool, Eq(false)); } INSTANTIATE_TEST_SUITE_P(FailStatus, ResultFailConversionTest, ValuesIn(AllFailStatusValues())); TEST(ResultMessage, SetMessageReturnsSelf) { Result result(Status::Ok); Result& other = result.SetMessage(""); EXPECT_THAT(&other, Eq(&result)); } TEST(ResultMessage, MessageDefaultsToEmpty) { Result result(Status::Ok); EXPECT_THAT(result.message(), Eq("")); } TEST(ResultMessage, SetMessageOnceSetsMessage) { Result result(Status::Ok); result.SetMessage("foo"); EXPECT_THAT(result.message(), Eq("foo")); } TEST(ResultMessage, SetMessageCopiesString) { Result result(Status::Ok); std::string original("foo"); result.SetMessage(original); EXPECT_THAT(result.message().data(), Not(Eq(original.data()))); } TEST(ResultMessage, SetMessageEmtpyStringPossible) { Result result(Status::Ok); result.SetMessage(""); EXPECT_THAT(result.message(), Eq("")); } TEST(ResultMessage, SetMessageTwiceRetainsLastMessage) { Result result(Status::Ok); result.SetMessage("foo"); result.SetMessage("bar baz"); EXPECT_THAT(result.message(), Eq("bar baz")); } } // namespace effcee-2019.1/effcee/to_string.h000066400000000000000000000015751367220107600164350ustar00rootroot00000000000000// Copyright 2018 The Effcee Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef EFFCEE_TO_STRING_H #define EFFCEE_TO_STRING_H #include #include "effcee.h" namespace effcee { // Returns a copy of a StringPiece, as a std::string. inline std::string ToString(effcee::StringPiece s) { return std::string(s.data(), s.size()); } } // namespace effcee #endif effcee-2019.1/examples/000077500000000000000000000000001367220107600146455ustar00rootroot00000000000000effcee-2019.1/examples/CMakeLists.txt000066400000000000000000000016001367220107600174020ustar00rootroot00000000000000add_executable(effcee-example main.cc) target_link_libraries(effcee-example effcee) if(UNIX AND NOT MINGW) set_target_properties(effcee-example PROPERTIES LINK_FLAGS -pthread) endif() if (WIN32 AND NOT MSVC) # For MinGW cross-compile, statically link to the C++ runtime set_target_properties(effcee-example PROPERTIES LINK_FLAGS "-static -static-libgcc -static-libstdc++") endif(WIN32 AND NOT MSVC) if(EFFCEE_BUILD_TESTING) add_test(NAME effcee-example COMMAND ${PYTHON_EXECUTABLE} effcee-example-driver.py $ example_data.txt "CHECK: Hello" "CHECK-SAME: world" "CHECK-NEXT: Bees" "CHECK-NOT: Sting" "CHECK: Honey" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) endif(EFFCEE_BUILD_TESTING) effcee-2019.1/examples/effcee-example-driver.py000066400000000000000000000024111367220107600213540ustar00rootroot00000000000000#!/usr/bin/env python3 # Copyright 2017 The Effcee Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Execute the effcee-example program, where the arguments are the location of the example program, the input file, and a list of check rules to match against the input. Args: effcee-example: Path to the effcee-example executable input_file: Data file containing the input to match check1 .. checkN: Check rules to match """ import subprocess import sys def main(): cmd = sys.argv[1] input_file = sys.argv[2] checks = sys.argv[3:] args = [cmd] args.extend(checks) print(args) with open(input_file) as input_stream: sys.exit(subprocess.call(args, stdin=input_stream)) sys.exit(1) if __name__ == '__main__': main() effcee-2019.1/examples/example_data.txt000066400000000000000000000000461367220107600200320ustar00rootroot00000000000000Hello world Bees Make Delicious Honey effcee-2019.1/examples/main.cc000066400000000000000000000041321367220107600161000ustar00rootroot00000000000000// Copyright 2017 The Effcee Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include "effcee/effcee.h" // Checks standard input against the list of checks provided as command line // arguments. // // Example: // cat <sample_data.txt // Bees // Make // Delicious Honey // EOF // effcee-example > input_stream.rdbuf(); // Attempt to match. The input and checks arguments can be provided as // std::string or pointer to char. auto result = effcee::Match(input_stream.str(), checks_stream.str(), effcee::Options().SetChecksName("checks")); // Successful match result converts to true. if (result) { std::cout << "The input matched your check list!" << std::endl; } else { // Otherwise, you can get a status code and a detailed message. switch (result.status()) { case effcee::Result::Status::NoRules: std::cout << "error: Expected check rules as command line arguments\n"; break; case effcee::Result::Status::Fail: std::cout << "The input failed to match your check rules:\n"; break; default: break; } std::cout << result.message() << std::endl; return 1; } return 0; } effcee-2019.1/fuzzer/000077500000000000000000000000001367220107600143545ustar00rootroot00000000000000effcee-2019.1/fuzzer/CMakeLists.txt000066400000000000000000000014311367220107600171130ustar00rootroot00000000000000if (EXISTS "${EFFCEE_FUZZED_DATA_PROVIDER_DIR}/FuzzedDataProvider.h") message(STATUS "effcee: configuring effcee-fuzz") add_executable(effcee-fuzz effcee_fuzz.cc) effcee_default_compile_options(effcee-fuzz) target_include_directories(effcee-fuzz PRIVATE "${EFFCEE_FUZZED_DATA_PROVIDER_DIR}") target_link_libraries(effcee-fuzz PRIVATE effcee) if(UNIX AND NOT MINGW) set_target_properties(effcee-fuzz PROPERTIES LINK_FLAGS -pthread) endif() if (WIN32 AND NOT MSVC) # For MinGW cross-compile, statically link to the C++ runtime set_target_properties(effcee-fuzz PROPERTIES LINK_FLAGS "-static -static-libgcc -static-libstdc++") endif(WIN32 AND NOT MSVC) else() message(STATUS "effcee: effcee-fuzz won't be built. Can't find FuzzedDataProvider.h") endif() effcee-2019.1/fuzzer/effcee_fuzz.cc000066400000000000000000000032101367220107600171520ustar00rootroot00000000000000// Copyright 2019 The Effcee Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include "FuzzedDataProvider.h" #include "effcee/effcee.h" // Consumes standard input as a fuzzer input, breaks it apart into text // and a check, then runs a basic match. int main(int argc, char* argv[]) { std::vector input; // Read standard input into a buffer. { if (FILE* fp = freopen(nullptr, "rb", stdin)) { uint8_t chunk[1024]; while (size_t len = fread(chunk, sizeof(uint8_t), sizeof(chunk), fp)) { input.insert(input.end(), chunk, chunk + len); } if (ftell(fp) == -1L) { if (ferror(fp)) { fprintf(stderr, "error: error reading standard input"); } return 1; } } else { fprintf(stderr, "error: couldn't reopen stdin for binary reading"); } } // This is very basic, but can find bugs. FuzzedDataProvider stream(input.data(), input.size()); std::string text = stream.ConsumeRandomLengthString(input.size()); std::string checks = stream.ConsumeRemainingBytesAsString(); effcee::Match(text, checks); return 0; } effcee-2019.1/third_party/000077500000000000000000000000001367220107600153605ustar00rootroot00000000000000effcee-2019.1/third_party/CMakeLists.txt000066400000000000000000000026251367220107600201250ustar00rootroot00000000000000# Suppress all warnings from third-party projects. set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS -w) # Set alternate root directory for third party sources. set(EFFCEE_THIRD_PARTY_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}" CACHE STRING "Root location of all third_party projects") # Find googletest and gmock if(${googletest-distribution_SOURCE_DIR}) set(EFFCEE_GOOGLETEST_DIR "${googletest-distribution_SOURCE_DIR}" CACHE STRING "Location of googletest source") else() set(EFFCEE_GOOGLETEST_DIR "${EFFCEE_THIRD_PARTY_ROOT_DIR}/googletest" CACHE STRING "Location of googletest source") endif() # Find re2 if(RE2_SOURCE_DIR) set(EFFCEE_RE2_DIR "${RE2_SOURCE_DIR}" CACHE STRING "Location of re2 source" FORCE) else() set(EFFCEE_RE2_DIR "${EFFCEE_THIRD_PARTY_ROOT_DIR}/re2" CACHE STRING "Location of re2 source") endif() # Configure third party projects. if(EFFCEE_BUILD_TESTING) if (NOT TARGET gmock) if (IS_DIRECTORY ${EFFCEE_GOOGLETEST_DIR}) add_subdirectory(${EFFCEE_GOOGLETEST_DIR} googletest EXCLUDE_FROM_ALL) endif() endif() if (NOT TARGET gmock) message(FATAL_ERROR "gmock was not found - required for tests") endif() endif() if (NOT TARGET re2) if (IS_DIRECTORY ${EFFCEE_RE2_DIR}) add_subdirectory(${EFFCEE_RE2_DIR} re2 EXCLUDE_FROM_ALL) endif() endif() if (NOT TARGET re2) message(FATAL_ERROR "re2 was not found - required for compilation") endif()