build(rust): vendor googletest crate
This commit was merged in pull request #76.
This commit is contained in:
1 parent
a6fe84190e
commit
6887b104d6
576 files changed
+284290
-2
No files matched your search
@@ -0,0 +1 @@
|
||||
{"$comment":"This file only protects against accidental modifications. It is not a security mechanism and does not protect against malicious changes.","files":{".cargo_vcs_info.json":"bd02a169c37bfb4d4180fde42695267a0ae90eae8f047d82ec43802aece8b342","Cargo.lock":"61eaa8a3d699f5246996d53825aea5cbd241011dd9c1adea60064f9dbbbf26e5","Cargo.toml":"9ec02c08ca5bf9ae77d9509b5c99d7bff70406bb773017bc7e7d45900e3e25bd","Cargo.toml.orig":"0e528fbf7a0659c362227b2be56bbe85a5b12e22e6efbdc81a03795df9fa602f","LICENSE":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","README.md":"416912f481d1b8c7580ebae4ed036e6c72adb5acdaef5e9e152f5d9ea4c5e479","src/lib.rs":"436715fbff5b895e88b288b8d40c521bb74b68f7202845a1f71483660b7624e6","src/matches_pattern.rs":"af3d59cf8235bc1da31f07fabcd3d769fb238767e77617a906f027df44333108","src/verify_pred.rs":"ad94e1afaffda7fcb4adaeb67422c33d2800fcb54e16a32edffdc33d95eb1b48"},"package":"2ae6abc96141edd26bf5aeec0f119c129c44de3ced09e5073711a02cb74725d0"}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "871f28b74e91e1d2a38e139ea03a9809d9d85f10"
|
||||
},
|
||||
"path_in_vcs": "googletest_macro"
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
###############################################################################
|
||||
# @generated
|
||||
# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To
|
||||
# regenerate this file, run the following:
|
||||
#
|
||||
# bazel run @@//third_party:vendor_crates
|
||||
###############################################################################
|
||||
|
||||
load("@rules_rust//cargo:defs.bzl", "cargo_toml_env_vars")
|
||||
load("@rules_rust//rust:defs.bzl", "rust_proc_macro")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
cargo_toml_env_vars(
|
||||
name = "cargo_toml_env_vars",
|
||||
src = "Cargo.toml",
|
||||
)
|
||||
|
||||
rust_proc_macro(
|
||||
name = "googletest_macro",
|
||||
srcs = glob(
|
||||
include = ["**/*.rs"],
|
||||
allow_empty = False,
|
||||
),
|
||||
compile_data = glob(
|
||||
include = ["**"],
|
||||
allow_empty = True,
|
||||
exclude = [
|
||||
"**/* *",
|
||||
".tmp_git_root/**/*",
|
||||
"BUILD",
|
||||
"BUILD.bazel",
|
||||
"WORKSPACE",
|
||||
"WORKSPACE.bazel",
|
||||
],
|
||||
),
|
||||
crate_root = "src/lib.rs",
|
||||
edition = "2021",
|
||||
rustc_env_files = [
|
||||
":cargo_toml_env_vars",
|
||||
],
|
||||
rustc_flags = [
|
||||
"--cap-lints=allow",
|
||||
],
|
||||
tags = [
|
||||
"cargo-bazel",
|
||||
"crate-name=googletest_macro",
|
||||
"manual",
|
||||
"noclippy",
|
||||
"norustfmt",
|
||||
],
|
||||
target_compatible_with = select({
|
||||
"@rules_rust//rust/platform:aarch64-apple-darwin": [],
|
||||
"@rules_rust//rust/platform:aarch64-apple-ios": [],
|
||||
"@rules_rust//rust/platform:aarch64-apple-ios-macabi": [],
|
||||
"@rules_rust//rust/platform:aarch64-apple-ios-sim": [],
|
||||
"@rules_rust//rust/platform:aarch64-linux-android": [],
|
||||
"@rules_rust//rust/platform:aarch64-pc-windows-msvc": [],
|
||||
"@rules_rust//rust/platform:aarch64-unknown-fuchsia": [],
|
||||
"@rules_rust//rust/platform:aarch64-unknown-linux-gnu": [],
|
||||
"@rules_rust//rust/platform:aarch64-unknown-nixos-gnu": [],
|
||||
"@rules_rust//rust/platform:aarch64-unknown-none": [],
|
||||
"@rules_rust//rust/platform:aarch64-unknown-nto-qnx710": [],
|
||||
"@rules_rust//rust/platform:aarch64-unknown-uefi": [],
|
||||
"@rules_rust//rust/platform:arm-unknown-linux-gnueabi": [],
|
||||
"@rules_rust//rust/platform:arm-unknown-linux-musleabi": [],
|
||||
"@rules_rust//rust/platform:armv7-linux-androideabi": [],
|
||||
"@rules_rust//rust/platform:armv7-unknown-linux-gnueabi": [],
|
||||
"@rules_rust//rust/platform:i686-apple-darwin": [],
|
||||
"@rules_rust//rust/platform:i686-linux-android": [],
|
||||
"@rules_rust//rust/platform:i686-pc-windows-msvc": [],
|
||||
"@rules_rust//rust/platform:i686-unknown-freebsd": [],
|
||||
"@rules_rust//rust/platform:i686-unknown-linux-gnu": [],
|
||||
"@rules_rust//rust/platform:loongarch64-unknown-linux-gnu": [],
|
||||
"@rules_rust//rust/platform:mips-unknown-linux-gnu": [],
|
||||
"@rules_rust//rust/platform:powerpc-unknown-linux-gnu": [],
|
||||
"@rules_rust//rust/platform:riscv32imac-unknown-none-elf": [],
|
||||
"@rules_rust//rust/platform:riscv32imc-unknown-none-elf": [],
|
||||
"@rules_rust//rust/platform:riscv64gc-unknown-linux-gnu": [],
|
||||
"@rules_rust//rust/platform:riscv64gc-unknown-none-elf": [],
|
||||
"@rules_rust//rust/platform:s390x-unknown-linux-gnu": [],
|
||||
"@rules_rust//rust/platform:sparc64-unknown-linux-gnu": [],
|
||||
"@rules_rust//rust/platform:sparc64-unknown-netbsd": [],
|
||||
"@rules_rust//rust/platform:sparc64-unknown-openbsd": [],
|
||||
"@rules_rust//rust/platform:thumbv6m-none-eabi": [],
|
||||
"@rules_rust//rust/platform:thumbv7em-none-eabi": [],
|
||||
"@rules_rust//rust/platform:thumbv7em-none-eabihf": [],
|
||||
"@rules_rust//rust/platform:thumbv7m-none-eabi": [],
|
||||
"@rules_rust//rust/platform:thumbv8m.main-none-eabi": [],
|
||||
"@rules_rust//rust/platform:thumbv8m.main-none-eabihf": [],
|
||||
"@rules_rust//rust/platform:wasm32-unknown-emscripten": [],
|
||||
"@rules_rust//rust/platform:wasm32-unknown-unknown": [],
|
||||
"@rules_rust//rust/platform:wasm32-wasip1": [],
|
||||
"@rules_rust//rust/platform:wasm32-wasip1-threads": [],
|
||||
"@rules_rust//rust/platform:wasm32-wasip2": [],
|
||||
"@rules_rust//rust/platform:x86_64-apple-darwin": [],
|
||||
"@rules_rust//rust/platform:x86_64-apple-ios": [],
|
||||
"@rules_rust//rust/platform:x86_64-apple-ios-macabi": [],
|
||||
"@rules_rust//rust/platform:x86_64-linux-android": [],
|
||||
"@rules_rust//rust/platform:x86_64-pc-windows-msvc": [],
|
||||
"@rules_rust//rust/platform:x86_64-unknown-freebsd": [],
|
||||
"@rules_rust//rust/platform:x86_64-unknown-fuchsia": [],
|
||||
"@rules_rust//rust/platform:x86_64-unknown-linux-gnu": [],
|
||||
"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu": [],
|
||||
"@rules_rust//rust/platform:x86_64-unknown-none": [],
|
||||
"@rules_rust//rust/platform:x86_64-unknown-uefi": [],
|
||||
"//conditions:default": ["@platforms//:incompatible"],
|
||||
}),
|
||||
version = "0.14.3",
|
||||
deps = [
|
||||
"//third_party/crates/proc-macro2-1.0.107:proc_macro2",
|
||||
"//third_party/crates/quote-1.0.47:quote",
|
||||
"//third_party/crates/syn-2.0.119:syn",
|
||||
],
|
||||
)
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "googletest_macro"
|
||||
version = "0.14.3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
|
||||
@@ -0,0 +1,54 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "googletest_macro"
|
||||
version = "0.14.3"
|
||||
authors = ["Bradford Hovinen <hovinen@google.com>"]
|
||||
build = false
|
||||
autolib = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "Procedural macros for GoogleTest Rust"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"unit",
|
||||
"matcher",
|
||||
"testing",
|
||||
"assertions",
|
||||
]
|
||||
categories = [
|
||||
"development-tools",
|
||||
"development-tools::testing",
|
||||
]
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/google/googletest-rust"
|
||||
|
||||
[lib]
|
||||
name = "googletest_macro"
|
||||
path = "src/lib.rs"
|
||||
proc-macro = true
|
||||
|
||||
[dependencies.proc-macro2]
|
||||
version = "1.0.106"
|
||||
|
||||
[dependencies.quote]
|
||||
version = "1.0.44"
|
||||
|
||||
[dependencies.syn]
|
||||
version = "2.0.114"
|
||||
features = [
|
||||
"full",
|
||||
"extra-traits",
|
||||
]
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# Copyright 2022 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
[package]
|
||||
name = "googletest_macro"
|
||||
version = "0.14.3"
|
||||
keywords = ["unit", "matcher", "testing", "assertions"]
|
||||
categories = ["development-tools", "development-tools::testing"]
|
||||
description = "Procedural macros for GoogleTest Rust"
|
||||
repository = "https://github.com/google/googletest-rust"
|
||||
license = "Apache-2.0"
|
||||
edition = "2021"
|
||||
authors = [
|
||||
"Bradford Hovinen <hovinen@google.com>",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
quote = "1.0.44"
|
||||
syn = {version = "2.0.114", features = ["full", "extra-traits"]}
|
||||
proc-macro2 = "1.0.106"
|
||||
|
||||
[lib]
|
||||
name = "googletest_macro"
|
||||
proc-macro = true
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,5 @@
|
||||
## Procedural macros for GoogleTest Rust
|
||||
|
||||
This crate is only useful as a dependency of
|
||||
[GoogleTest Rust](https://crates.io/crates/googletest). It cannot be used on its
|
||||
own.
|
||||
@@ -0,0 +1,415 @@
|
||||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, Attribute,
|
||||
DeriveInput, Expr, ExprLit, FnArg, ItemFn, Lit, MetaNameValue, PatType, ReturnType, Signature,
|
||||
Type,
|
||||
};
|
||||
|
||||
/// Marks a test to be run by the Google Rust test runner.
|
||||
///
|
||||
/// Annotate tests the same way ordinary Rust tests are annotated:
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[gtest]
|
||||
/// fn should_work() {
|
||||
/// ...
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The test function is not required to have a return type. If it does have a
|
||||
/// return type, that type must be [`googletest::Result`]. One may do this if
|
||||
/// one wishes to use both fatal and non-fatal assertions in the same test. For
|
||||
/// example:
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[gtest]
|
||||
/// fn should_work() -> googletest::Result<()> {
|
||||
/// let value = 2;
|
||||
/// expect_that!(value, gt(0));
|
||||
/// verify_that!(value, eq(2))
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This macro can be used with `#[should_panic]` to indicate that the test is
|
||||
/// expected to panic. For example:
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[gtest]
|
||||
/// #[should_panic]
|
||||
/// fn passes_due_to_should_panic() {
|
||||
/// let value = 2;
|
||||
/// expect_that!(value, gt(0));
|
||||
/// panic!("This panics");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Using `#[should_panic]` modifies the behaviour of `#[gtest]` so
|
||||
/// that the test panics (and passes) if any non-fatal assertion occurs.
|
||||
/// For example, the following test passes:
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[gtest]
|
||||
/// #[should_panic]
|
||||
/// fn passes_due_to_should_panic_and_failing_assertion() {
|
||||
/// let value = 2;
|
||||
/// expect_that!(value, eq(0));
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`googletest::Result`]: type.Result.html
|
||||
#[proc_macro_attribute]
|
||||
pub fn gtest(
|
||||
_args: proc_macro::TokenStream,
|
||||
input: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
let ItemFn { mut attrs, sig, block, .. } = parse_macro_input!(input as ItemFn);
|
||||
|
||||
let sig_ident = &sig.ident;
|
||||
let test_case_hash: u64 = {
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut h = DefaultHasher::new();
|
||||
|
||||
// Only consider attrs and name for stability. Changing the function body should
|
||||
// not affect the test case distribution.
|
||||
attrs.hash(&mut h);
|
||||
sig_ident.hash(&mut h);
|
||||
h.finish()
|
||||
};
|
||||
|
||||
let (skipped_test_result, outer_return_type, trailer) = attrs
|
||||
.iter()
|
||||
.find(|attr| attr.path().is_ident("should_panic"))
|
||||
.map(|attr| {
|
||||
let error_message = extract_should_panic_expected(attr).unwrap_or("".to_string());
|
||||
(
|
||||
quote! {
|
||||
{
|
||||
panic!("{}", #error_message);
|
||||
}
|
||||
},
|
||||
quote! { () },
|
||||
quote! { .unwrap(); }
|
||||
)})
|
||||
.unwrap_or_else(||
|
||||
(
|
||||
quote! {Ok(())},
|
||||
quote! { ::core::result::Result<(), googletest::internal::test_outcome::TestFailure> },
|
||||
quote! {},
|
||||
));
|
||||
|
||||
let is_rstest_enabled = is_rstest_enabled(&attrs);
|
||||
let outer_sig = {
|
||||
let mut outer_sig = sig.clone();
|
||||
outer_sig.output = ReturnType::Default;
|
||||
if !is_rstest_enabled {
|
||||
outer_sig.inputs = Punctuated::new();
|
||||
}
|
||||
outer_sig
|
||||
};
|
||||
|
||||
let output_type = match sig.output {
|
||||
ReturnType::Default => None,
|
||||
ReturnType::Type(_, ref ty) => Some(quote! {#ty}),
|
||||
};
|
||||
let is_async = sig.asyncness.is_some();
|
||||
let maybe_async = is_async.then(|| quote! { async });
|
||||
let maybe_await = is_async.then(|| quote! { .await });
|
||||
let invocation = if is_rstest_enabled {
|
||||
// Rstest may refer in block to its fixtures. Hence, we only wrap it in a
|
||||
// closure to capture them.
|
||||
let result_type = output_type.clone().unwrap_or_else(|| quote! {()});
|
||||
let mut invocation = quote! {
|
||||
(#maybe_async move || -> #result_type {
|
||||
#block
|
||||
})() #maybe_await
|
||||
};
|
||||
if output_type.is_none() {
|
||||
invocation = quote! { { let () = #invocation; googletest::Result::<()>::Ok(()) } };
|
||||
}
|
||||
invocation
|
||||
} else {
|
||||
let closure_body = match closure_body(&sig) {
|
||||
Ok(body) => body,
|
||||
Err(e) => return e.into_compile_error().into(),
|
||||
};
|
||||
|
||||
// In the sync case, the ? operator returns from the surrounding function. So
|
||||
// we redeclare the original test function internally.
|
||||
let result_type = output_type.unwrap_or_else(|| quote! {googletest::Result<()>});
|
||||
quote! {
|
||||
(#maybe_async move || -> #result_type {
|
||||
#sig { #block }
|
||||
#closure_body
|
||||
})() #maybe_await
|
||||
}
|
||||
};
|
||||
if !attrs.iter().any(is_test_attribute) && !is_rstest_enabled {
|
||||
let test_attr: Attribute = parse_quote! {
|
||||
#[::core::prelude::v1::test]
|
||||
};
|
||||
attrs.push(test_attr);
|
||||
};
|
||||
let function = quote! {
|
||||
#(#attrs)*
|
||||
#outer_sig -> #outer_return_type {
|
||||
if !googletest::internal::test_filter::test_should_run(concat!(module_path!(), "::", stringify!(#sig_ident))) {
|
||||
#skipped_test_result
|
||||
} else if googletest::internal::test_sharding::test_should_run(#test_case_hash) {
|
||||
use googletest::internal::test_outcome::TestOutcome;
|
||||
TestOutcome::init_current_test_outcome();
|
||||
TestOutcome::close_current_test_outcome(#invocation)
|
||||
} else {
|
||||
#skipped_test_result
|
||||
}
|
||||
#trailer
|
||||
}
|
||||
};
|
||||
function.into()
|
||||
}
|
||||
|
||||
/// Extract the optional "expected" string literal from a `should_panic`
|
||||
/// attribute.
|
||||
fn extract_should_panic_expected(attr: &Attribute) -> Option<String> {
|
||||
let Ok(name_value) = attr.parse_args::<MetaNameValue>() else {
|
||||
return None;
|
||||
};
|
||||
match name_value.value {
|
||||
Expr::Lit(ExprLit { lit: Lit::Str(expected), .. })
|
||||
if name_value.path.is_ident("expected") =>
|
||||
{
|
||||
Some(expected.value())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Alias for [`googletest::gtest`].
|
||||
///
|
||||
/// Generally, prefer using `#[gtest]` to mark googletest-based tests.
|
||||
///
|
||||
/// Use `#[test]` instead of `#[gtest]` to satisfy compatibility
|
||||
/// requirements. For example, the rstest crate can be composed with other test
|
||||
/// attributes but it requires the attribute to be named `test`.
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[rstest]
|
||||
/// #[googletest::test]
|
||||
/// fn rstest_with_googletest() -> Result<()> {
|
||||
/// verify_that!(1, eq(1))
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`googletest::gtest`]: attr.gtest.html
|
||||
#[proc_macro_attribute]
|
||||
pub fn test(
|
||||
args: proc_macro::TokenStream,
|
||||
input: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
gtest(args, input)
|
||||
}
|
||||
|
||||
fn is_test_attribute(attr: &Attribute) -> bool {
|
||||
match attr.path().segments.last() {
|
||||
Some(last_segment) => last_segment.ident == "test",
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_rstest_enabled(attributes: &[Attribute]) -> bool {
|
||||
attributes.iter().any(|attr| matches!(attr.path().segments.last(), Some(last_segment) if last_segment.ident == "rstest"))
|
||||
}
|
||||
|
||||
enum FixtureKind {
|
||||
Consumable,
|
||||
MutableRef,
|
||||
SharedRef,
|
||||
}
|
||||
|
||||
struct Fixture {
|
||||
identifier: syn::Ident,
|
||||
ty: Box<syn::Type>,
|
||||
kind: FixtureKind,
|
||||
}
|
||||
|
||||
impl Fixture {
|
||||
fn new(index: usize, ty: Box<syn::Type>) -> syn::Result<Self> {
|
||||
let identifier = syn::Ident::new(&format!("__googletest__fixture__{index}"), ty.span());
|
||||
match &*ty {
|
||||
Type::Reference(reference) => Ok(Self {
|
||||
identifier,
|
||||
ty: reference.elem.clone(),
|
||||
kind: if reference.mutability.is_some() {
|
||||
FixtureKind::MutableRef
|
||||
} else {
|
||||
FixtureKind::SharedRef
|
||||
},
|
||||
}),
|
||||
Type::Path(..) => Ok(Self { identifier, ty, kind: FixtureKind::Consumable }),
|
||||
_ => Err(syn::Error::new(
|
||||
ty.span(),
|
||||
"Unexpected fixture type. Only references (&T or &mut T) and paths (T) are supported.",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_call(&self, inner_call: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
|
||||
let Self { identifier, ty, kind } = self;
|
||||
let ref_method = match kind {
|
||||
FixtureKind::Consumable => {
|
||||
return quote! {
|
||||
#[allow(non_snake_case)]
|
||||
let #identifier = <#ty as googletest::fixtures::ConsumableFixture>::set_up()?;
|
||||
{ #inner_call }
|
||||
};
|
||||
}
|
||||
FixtureKind::MutableRef => quote! { .as_mut() },
|
||||
FixtureKind::SharedRef => quote! { .as_ref() },
|
||||
};
|
||||
quote! { {
|
||||
#[allow(non_snake_case, unused_mut)]
|
||||
let mut #identifier =
|
||||
googletest::__internal_macro_support::FixtureTearDownOnDrop::new(
|
||||
<#ty as googletest::fixtures::Fixture>::set_up()?);
|
||||
let result = {
|
||||
#[allow(non_snake_case)]
|
||||
let #identifier = #identifier #ref_method;
|
||||
#inner_call
|
||||
};
|
||||
#identifier.tear_down()?;
|
||||
result
|
||||
} }
|
||||
}
|
||||
}
|
||||
|
||||
fn closure_body(signature: &Signature) -> syn::Result<proc_macro2::TokenStream> {
|
||||
let input_types = signature
|
||||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, typed)| {
|
||||
let FnArg::Typed(PatType { ty, .. }) = typed else {
|
||||
return Err(syn::Error::new(
|
||||
typed.span(),
|
||||
"`self` receiver is not accepted as test argument",
|
||||
));
|
||||
};
|
||||
Fixture::new(index, ty.clone())
|
||||
})
|
||||
.collect::<syn::Result<Vec<Fixture>>>()?;
|
||||
|
||||
let mut block = {
|
||||
let parameters = input_types.iter().map(|fixture| &fixture.identifier);
|
||||
|
||||
let test_name = &signature.ident;
|
||||
let mut invocation = quote! {
|
||||
#test_name(#(#parameters, )*)
|
||||
};
|
||||
if signature.asyncness.is_some() {
|
||||
invocation = quote! { #invocation.await };
|
||||
}
|
||||
if let ReturnType::Default = signature.output {
|
||||
invocation = quote! { { let () = #invocation; googletest::Result::Ok(()) } };
|
||||
}
|
||||
invocation
|
||||
};
|
||||
|
||||
for fixture in input_types.iter().rev() {
|
||||
block = fixture.wrap_call(block);
|
||||
}
|
||||
|
||||
Ok(block)
|
||||
}
|
||||
|
||||
#[proc_macro_derive(MatcherBase)]
|
||||
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
let DeriveInput { ident, generics, .. } = ast;
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
quote! {
|
||||
impl #impl_generics MatcherBase for #ident #ty_generics #where_clause {}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
mod matches_pattern;
|
||||
|
||||
/// This is an implementation detail of `googletest::matches_pattern!`.
|
||||
///
|
||||
/// It's not intended to be used directly.
|
||||
#[doc(hidden)]
|
||||
#[proc_macro]
|
||||
pub fn __googletest_macro_matches_pattern(
|
||||
input: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
matches_pattern::matches_pattern_impl(input)
|
||||
}
|
||||
|
||||
mod verify_pred;
|
||||
|
||||
/// This is an implementation detail of `googletest::verify_pred!`.
|
||||
///
|
||||
/// It's not intended to be used directly.
|
||||
#[doc(hidden)]
|
||||
#[proc_macro]
|
||||
pub fn __googletest_macro_verify_pred(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
verify_pred::verify_pred_impl(input)
|
||||
}
|
||||
|
||||
/// Stringifies its argument (like `stringify!()` from the standard library) but
|
||||
/// limits the output to a provided maximum length.
|
||||
///
|
||||
/// The input is a tuple of `target` and `max_length` seprated by a comma.
|
||||
/// The `max_length` is the maximum number of characters to include in the
|
||||
/// abbreviated string. For example:
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[rstest]
|
||||
/// #[gtest]
|
||||
/// fn test_abbreviated_string() -> Result<()> {
|
||||
/// verifiy_eq!(__abbreviated_stringify!(|x| x + 1, 6), "|x|...")?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[doc(hidden)]
|
||||
#[proc_macro]
|
||||
pub fn __abbreviated_stringify(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input = input.to_string();
|
||||
let abbreviated = abbreviated_string(&input).unwrap();
|
||||
quote! {
|
||||
#abbreviated
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
fn abbreviated_string(target: &str) -> Result<std::borrow::Cow<'_, str>, &'static str> {
|
||||
use std::borrow::Cow;
|
||||
match target.rsplit_once(',') {
|
||||
None => Err("Expect a `max_length` argument, but got none"),
|
||||
Some((expr, limit)) => match limit.trim().parse::<usize>() {
|
||||
Ok(limit) if expr.len() > limit => {
|
||||
if limit >= 4 {
|
||||
Ok(Cow::Owned(format!("{}...", &expr[..limit - 3])))
|
||||
} else {
|
||||
Err("The `max_length` argument is too small. It must be at least 4.")
|
||||
}
|
||||
}
|
||||
Ok(_) => Ok(Cow::Borrowed(expr)),
|
||||
Err(_) => Err("The `max_length` argument is not a number."),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream, Parser as _},
|
||||
parse_macro_input,
|
||||
token::DotDot,
|
||||
Expr, ExprCall, Pat, Token,
|
||||
};
|
||||
|
||||
/// This is an implementation detail of `googletest::matches_pattern!`. It
|
||||
/// assumes that a few symbols from `googletest::matchers` have been imported
|
||||
/// and that `$crate` has been aliased to `googletest` (which might otherwise
|
||||
/// have been imported as a different alias), both of which are done
|
||||
/// by `googletest::matches_pattern!` before calling this proc macro.
|
||||
pub(crate) fn matches_pattern_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let result = parse_macro_input!(input as ParsedMatchPattern).into_matcher_expr();
|
||||
quote! { #result }.into()
|
||||
}
|
||||
|
||||
/// Represents one of the root-level pseudo-patterns supported by
|
||||
/// `match_pattern!`.
|
||||
///
|
||||
/// Examples with `struct_name` set and `group` being `None`:
|
||||
/// * `true`
|
||||
/// * `Enum::Variant`
|
||||
/// * `&Enum::Variant`
|
||||
///
|
||||
/// Examples with `group` being non-`None`.
|
||||
/// * `Struct { a: eq(1), b: ends_with("foo") }`
|
||||
/// * `&Struct(ref eq(&1), ends_with("foo"))`
|
||||
#[derive(Debug)]
|
||||
struct ParsedMatchPattern {
|
||||
struct_name: TokenStream,
|
||||
group: Option<Group>,
|
||||
}
|
||||
|
||||
impl Parse for ParsedMatchPattern {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let mut struct_name: Vec<TokenTree> = vec![];
|
||||
let mut group: Option<Group> = None;
|
||||
|
||||
// Use a TT-muncher (as opposed to parsing `Expr` or `Pat` or such) since:
|
||||
// - The `struct_name` can be a struct/enum type or literal value (`true`, `1`,
|
||||
// `&"test"`).
|
||||
// - The braced part supports syntax that is not valid rust like `&Foo { a: ref
|
||||
// eq(1) }`.
|
||||
input.step(|cursor| {
|
||||
let mut rest = *cursor;
|
||||
|
||||
while let Some((tt, next)) = rest.token_tree() {
|
||||
// If we reached a group (`{...}` or `(...)`), that should be the end of the
|
||||
// pattern. Other groups (`[...]`) are not supported in struct names, but that's
|
||||
// checked later.
|
||||
if let TokenTree::Group(g) = tt {
|
||||
group = Some(g);
|
||||
return Ok(((), next));
|
||||
}
|
||||
|
||||
// Everything before the group is the struct/enum name, possibly prefixed with
|
||||
// `&`.
|
||||
struct_name.push(tt);
|
||||
rest = next;
|
||||
}
|
||||
|
||||
// If no group found, remove any trailing comma that might have been
|
||||
// incorporated into the struct/enum name.
|
||||
if matches!(struct_name.last(), Some(TokenTree::Punct(p)) if p.as_char() == ',') {
|
||||
struct_name.pop();
|
||||
}
|
||||
|
||||
Ok(((), rest))
|
||||
})?;
|
||||
|
||||
input.parse::<Option<Token![,]>>()?;
|
||||
let struct_name = struct_name.into_iter().collect();
|
||||
Ok(ParsedMatchPattern { struct_name, group })
|
||||
}
|
||||
}
|
||||
|
||||
impl ParsedMatchPattern {
|
||||
fn into_matcher_expr(self) -> TokenStream {
|
||||
let Self { struct_name, group } = self;
|
||||
// `matcher_pattern` supports both its custom (not necessarily valid rust)
|
||||
// syntax and also native match pattern (like `Struct { .. }` and
|
||||
// `Struct(_)`). So we need to speculatively attempt the first and then
|
||||
// fall back to the latter if the first fails. If both fail, we return
|
||||
// the error from the first attempt.
|
||||
let mut first_err = None;
|
||||
|
||||
// `matcher_pattern` special syntax.
|
||||
if let Some(ref g) = group {
|
||||
let res = match g.delimiter() {
|
||||
Delimiter::Parenthesis => parse_tuple_pattern_args(struct_name.clone(), g.stream()),
|
||||
Delimiter::Brace => parse_braced_pattern_args(struct_name.clone(), g.stream()),
|
||||
Delimiter::Bracket => compile_err(g.span(), "[...] syntax is not meaningful"),
|
||||
Delimiter::None => compile_err(g.span(), "undelimited group not supported"),
|
||||
};
|
||||
match res {
|
||||
Ok(res) => return res,
|
||||
Err(e) => first_err = Some(e),
|
||||
}
|
||||
}
|
||||
|
||||
// Standard `match` pattern (prioritize `first_err` if both fail).
|
||||
into_match_pattern_expr(quote! { #struct_name #group })
|
||||
.map_err(|e| first_err.unwrap_or(e))
|
||||
.unwrap_or_else(syn::Error::into_compile_error)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a pattern match expression as long as `stream` is a valid pattern.
|
||||
/// Otherwise, returns failure.
|
||||
fn into_match_pattern_expr(stream: TokenStream) -> syn::Result<TokenStream> {
|
||||
// Only produce if stream successfully parses as a pattern. Otherwise, return
|
||||
// failure so that we can instead return the error due to failing to parse
|
||||
// the `matcher_pattern` custom syntax.
|
||||
Pat::parse_multi.parse2(stream.clone())?;
|
||||
Ok(quote! {
|
||||
googletest::matchers::__internal_unstable_do_not_depend_on_these::pattern_only(
|
||||
|v| matches!(v, #stream),
|
||||
concat!("is ", stringify!(#stream)),
|
||||
concat!("is not ", stringify!(#stream)))
|
||||
})
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Parse tuple struct patterns
|
||||
|
||||
/// Each part in a tuple matcher pattern that's between the commas. When `None`,
|
||||
/// it represents `_` which matches anything.
|
||||
struct MaybeTupleFieldPattern(Option<TupleFieldPattern>);
|
||||
|
||||
struct TupleFieldPattern {
|
||||
ref_token: Option<Token![ref]>,
|
||||
matcher: Expr,
|
||||
}
|
||||
|
||||
impl Parse for MaybeTupleFieldPattern {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let pattern = match input.parse::<Option<Token![_]>>()? {
|
||||
Some(_) => None,
|
||||
None => Some(TupleFieldPattern { ref_token: input.parse()?, matcher: input.parse()? }),
|
||||
};
|
||||
Ok(MaybeTupleFieldPattern(pattern))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a tuple struct's fields into a match expression.
|
||||
fn parse_tuple_pattern_args(
|
||||
struct_name: TokenStream,
|
||||
group_content: TokenStream,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let (patterns, dot_dot) =
|
||||
parse_list_terminated_pattern::<MaybeTupleFieldPattern>.parse2(group_content)?;
|
||||
let field_count = patterns.len();
|
||||
let field_patterns: Vec<_> = patterns
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|(index, maybe_pattern)| maybe_pattern.0.map(|pattern| (index, pattern)))
|
||||
.map(|(index, TupleFieldPattern { ref_token, matcher })| {
|
||||
let index = syn::Index::from(index);
|
||||
quote! { googletest::matchers::field!(#struct_name.#index, #ref_token #matcher) }
|
||||
})
|
||||
.collect();
|
||||
|
||||
if field_patterns.is_empty() {
|
||||
// It is possible that the logic above didn't generate any field matchers
|
||||
// (e.g., for patterns like `Some(_)`).
|
||||
// In this case we verify that the enum has the correct case, but don't
|
||||
// verify the payload.
|
||||
#[allow(clippy::manual_repeat_n)]
|
||||
// `repeat_n` is not available on the Rust MSRV that we support in OSS
|
||||
let ignored_fields = std::iter::repeat(quote! { _ })
|
||||
.take(field_count)
|
||||
.chain(dot_dot.map(ToTokens::into_token_stream));
|
||||
let full_pattern = quote! { #struct_name ( #(#ignored_fields),* ) };
|
||||
|
||||
Ok(quote! {
|
||||
googletest::matchers::__internal_unstable_do_not_depend_on_these::pattern_only(
|
||||
|actual| { matches!(actual, #full_pattern) },
|
||||
concat!("is ", stringify!(#full_pattern)),
|
||||
concat!("is not ", stringify!(#full_pattern))
|
||||
)
|
||||
})
|
||||
} else {
|
||||
// We have created at least one field matcher. Each field matcher will verify
|
||||
// not only its part of the payload, but also that the enum has the
|
||||
// correct case.
|
||||
let matcher = quote! {
|
||||
googletest::matchers::__internal_unstable_do_not_depend_on_these::is(
|
||||
stringify!(#struct_name),
|
||||
all!( #(#field_patterns),* )
|
||||
)
|
||||
};
|
||||
|
||||
// Do a match to ensure:
|
||||
// - Fields are exhaustively listed unless the pattern ended with `..`.
|
||||
// - `UNDEFINED_SYMBOL(..)` fails to compile.
|
||||
#[allow(clippy::manual_repeat_n)]
|
||||
// `repeat_n` is not available on the Rust MSRV that we support in OSS
|
||||
let empty_fields = std::iter::repeat(quote! { _ })
|
||||
.take(field_count)
|
||||
.chain(dot_dot.map(ToTokens::into_token_stream));
|
||||
Ok(quote! {
|
||||
googletest::matchers::__internal_unstable_do_not_depend_on_these::compile_assert_and_match(
|
||||
|actual| {
|
||||
// Exhaustively check that all field names are specified.
|
||||
match actual {
|
||||
#struct_name ( #(#empty_fields),* ) => (),
|
||||
// The pattern below is unreachable if the type is a struct (as opposed to
|
||||
// an enum). Since the macro can't know which it is, we always include it
|
||||
// and just tell the compiler not to complain.
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
#matcher)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Parse braced structs patterns
|
||||
|
||||
enum FieldOrMethod {
|
||||
Field(Ident),
|
||||
Method(ExprCall),
|
||||
}
|
||||
|
||||
impl Parse for FieldOrMethod {
|
||||
/// Parses the field name or method call along with the `:` that follows it.
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
// If the ident is followed by a single `:` and not a `::`, then it's a field
|
||||
// match and not a method call match.
|
||||
let value = if input.peek2(Token![:]) && !input.peek2(Token![::]) {
|
||||
input.parse().map(FieldOrMethod::Field)
|
||||
} else {
|
||||
input.parse().map(FieldOrMethod::Method)
|
||||
}?;
|
||||
input.parse::<Token![:]>()?;
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Either field or method call matcher. E.g.:
|
||||
/// * `field: starts_with("something")` or `field: _`
|
||||
/// * `property(arg1, arg2): starts_with("something")
|
||||
struct FieldOrMethodPattern {
|
||||
ref_token: Option<Token![ref]>,
|
||||
field_or_method: FieldOrMethod,
|
||||
/// When `None`, it represents `_` which matches anything, meaning we should
|
||||
/// ignore it.
|
||||
matcher: Option<Expr>,
|
||||
}
|
||||
|
||||
impl Parse for FieldOrMethodPattern {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let field_or_method: FieldOrMethod = input.parse()?;
|
||||
let underscore = input.parse::<Option<Token![_]>>()?;
|
||||
match underscore {
|
||||
Some(underscore) if matches!(field_or_method, FieldOrMethod::Method(_)) => compile_err(
|
||||
underscore.spans[0],
|
||||
"Don't match a method call against `_`. Just omit it instead.",
|
||||
),
|
||||
Some(_) => Ok(FieldOrMethodPattern { field_or_method, ref_token: None, matcher: None }),
|
||||
None => Ok(FieldOrMethodPattern {
|
||||
field_or_method,
|
||||
ref_token: input.parse()?,
|
||||
matcher: Some(input.parse()?),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a struct's fields or method calls into a match expression.
|
||||
fn parse_braced_pattern_args(
|
||||
struct_name: TokenStream,
|
||||
group_content: TokenStream,
|
||||
) -> syn::Result<TokenStream> {
|
||||
let (patterns, dot_dot) = parse_list_terminated_pattern.parse2(group_content)?;
|
||||
let mut field_names = vec![];
|
||||
let field_patterns: Vec<TokenStream> = patterns
|
||||
.into_iter()
|
||||
.filter_map(|FieldOrMethodPattern { ref_token, field_or_method, matcher }| {
|
||||
match field_or_method {
|
||||
FieldOrMethod::Field(ident) => {
|
||||
field_names.push(ident.clone());
|
||||
matcher.map(|matcher| {
|
||||
quote! { field!(#struct_name . #ident, #ref_token #matcher) }
|
||||
})
|
||||
}
|
||||
FieldOrMethod::Method(call) => {
|
||||
Some(quote! { property!(#struct_name . #call, #ref_token #matcher) })
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
if field_patterns.is_empty() {
|
||||
// It is possible that the logic above didn't generate any field matchers
|
||||
// (e.g., for patterns like `AnEnum::Foo { a_field : _ }`).
|
||||
// In this case we verify that the enum has the correct case, but don't
|
||||
// verify the payload.
|
||||
let full_pattern = quote! { #struct_name { #(#field_names: _,)* #dot_dot}};
|
||||
|
||||
Ok(quote! {
|
||||
googletest::matchers::__internal_unstable_do_not_depend_on_these::pattern_only(
|
||||
|actual| { matches!(actual, #full_pattern) },
|
||||
concat!("is ", stringify!(#full_pattern)),
|
||||
concat!("is not ", stringify!(#full_pattern))
|
||||
)
|
||||
})
|
||||
} else {
|
||||
// We have created at least one field matcher. Each field matcher will verify
|
||||
// not only its part of the payload, but also that the enum has the
|
||||
// correct case.
|
||||
let matcher = quote! {
|
||||
googletest::matchers::__internal_unstable_do_not_depend_on_these::is(
|
||||
stringify!(#struct_name),
|
||||
all!(#(#field_patterns),* )
|
||||
)
|
||||
};
|
||||
|
||||
// Do a match to ensure:
|
||||
// - Fields are exhaustively listed unless the pattern ended with `..` and has
|
||||
// any fields in the pattern.
|
||||
// - `UNDEFINED_SYMBOL { .. }` fails to compile.
|
||||
//
|
||||
// The requisite that some fields are in the pattern is there because
|
||||
// `matches_pattern!` also uses the brace notation for tuple structs when
|
||||
// asserting on method calls on tuple structs. i.e.
|
||||
//
|
||||
// ```
|
||||
// struct Struct(u32);
|
||||
// ...
|
||||
// matches_pattern!(foo, Struct { bar(): eq(1) })
|
||||
// ```
|
||||
// and we can't emit an exhaustiveness check based on the `matches_pattern!`.
|
||||
if field_names.is_empty() && dot_dot.is_none() &&
|
||||
// If there are no fields, then this check means that there are method patterns, and we can
|
||||
// no longer be confident that this is a braced struct rather than a tuple struct.
|
||||
!field_patterns.is_empty()
|
||||
{
|
||||
Ok(matcher)
|
||||
} else {
|
||||
Ok(quote! {
|
||||
googletest::matchers::__internal_unstable_do_not_depend_on_these::compile_assert_and_match(
|
||||
|actual| {
|
||||
// Exhaustively check that all field names are specified.
|
||||
match actual {
|
||||
#struct_name { #(#field_names: _,)* #dot_dot } => {},
|
||||
// The pattern below is unreachable if the type is a struct (as opposed to
|
||||
// an enum). Since the macro can't know which it is, we always include it
|
||||
// and just tell the compiler not to complain.
|
||||
#[allow(unreachable_patterns)]
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
#matcher)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// General-purpose helpers
|
||||
|
||||
/// Returns the parsed struct pattern body along with a `..` if it appears at
|
||||
/// the end of the body.
|
||||
///
|
||||
/// This is like `Punctuated::parse_terminated`, but additionally allows for an
|
||||
/// optional `..`, which cannot be followed by a comma.
|
||||
fn parse_list_terminated_pattern<T: Parse>(
|
||||
input: ParseStream<'_>,
|
||||
) -> syn::Result<(Vec<T>, Option<DotDot>)> {
|
||||
let mut patterns = vec![];
|
||||
while !input.is_empty() {
|
||||
// Check for trailing `..`.
|
||||
let dot_dot = input.parse::<Option<Token![..]>>()?;
|
||||
if dot_dot.is_some() {
|
||||
// Must be at the end of the group content.
|
||||
return if input.is_empty() {
|
||||
Ok((patterns, dot_dot))
|
||||
} else {
|
||||
compile_err(input.span(), "`..` must be at the end of the struct pattern")
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise, parse the field/method patterns.
|
||||
patterns.push(input.parse::<T>()?);
|
||||
if input.is_empty() {
|
||||
break;
|
||||
}
|
||||
input.parse::<Token![,]>()?;
|
||||
}
|
||||
Ok((patterns, None))
|
||||
}
|
||||
|
||||
fn compile_err<T>(span: Span, message: &str) -> syn::Result<T> {
|
||||
Err(syn::Error::new(span, message))
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
// Copyright 2024 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, punctuated::Punctuated, spanned::Spanned, token::Comma, Expr, Ident};
|
||||
|
||||
struct AccumulatePartsState {
|
||||
var_num: usize,
|
||||
error_message_ident: Ident,
|
||||
statements: Vec<proc_macro2::TokenStream>,
|
||||
}
|
||||
|
||||
fn expr_to_string(expr: &Expr) -> String {
|
||||
quote!(#expr).to_string()
|
||||
}
|
||||
|
||||
impl AccumulatePartsState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
var_num: 0,
|
||||
error_message_ident: Ident::new(
|
||||
"__googletest__verify_pred__error_message",
|
||||
::proc_macro2::Span::call_site(),
|
||||
),
|
||||
statements: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes an expression with chained field accesses and method calls and
|
||||
/// accumulates intermediate expressions used for computing `verify_pred!`'s
|
||||
/// expression, including intermediate variable assignments to evaluate
|
||||
/// parts of the expression exactly once, and the format string used to
|
||||
/// output intermediate values on condition failure. It returns the new form
|
||||
/// of the input expression with parts of it potentially replaced by the
|
||||
/// intermediate variables.
|
||||
fn accumulate_parts(&mut self, expr: Expr) -> Expr {
|
||||
// Literals don't need to be printed or stored in intermediate variables.
|
||||
if is_literal(&expr) {
|
||||
return expr;
|
||||
}
|
||||
let expr_string = expr_to_string(&expr);
|
||||
let new_expr = match expr {
|
||||
Expr::Group(mut group) => {
|
||||
// This is an invisible group added for correct precedence in the AST. Just pass
|
||||
// through without having a separate printing result.
|
||||
*group.expr = self.accumulate_parts(*group.expr);
|
||||
return Expr::Group(group);
|
||||
}
|
||||
Expr::Field(mut field) => {
|
||||
// Don't assign field access to an intermediate variable to avoid moving out of
|
||||
// non-`Copy` fields.
|
||||
*field.base = self.accumulate_parts(*field.base);
|
||||
Expr::Field(field)
|
||||
}
|
||||
Expr::Call(mut call) => {
|
||||
// Cache args into intermediate variables.
|
||||
call.args = self.define_variables_for_args(call.args);
|
||||
// Cache function value into an intermediate variable.
|
||||
self.define_variable(&Expr::Call(call))
|
||||
}
|
||||
Expr::MethodCall(mut method_call) => {
|
||||
*method_call.receiver = self.accumulate_parts(*method_call.receiver);
|
||||
// Cache args into intermediate variables.
|
||||
method_call.args = self.define_variables_for_args(method_call.args);
|
||||
// Cache method value into an intermediate variable.
|
||||
self.define_variable(&Expr::MethodCall(method_call))
|
||||
}
|
||||
Expr::Binary(mut binary) => {
|
||||
*binary.left = self.accumulate_parts(*binary.left);
|
||||
*binary.right = self.accumulate_parts(*binary.right);
|
||||
Expr::Binary(binary)
|
||||
}
|
||||
Expr::Unary(mut unary) => {
|
||||
*unary.expr = self.accumulate_parts(*unary.expr);
|
||||
Expr::Unary(unary)
|
||||
}
|
||||
// A path expression doesn't need to be stored in an intermediate variable.
|
||||
// This avoids moving out of an existing variable.
|
||||
Expr::Path(_) => expr,
|
||||
// By default, assume it's some expression that needs to be cached to avoid
|
||||
// double-evaluation.
|
||||
_ => self.define_variable(&expr),
|
||||
};
|
||||
let error_message_ident = &self.error_message_ident;
|
||||
self.statements.push(quote! {
|
||||
::googletest::fmt::internal::__googletest__write_expr_value!(
|
||||
&mut #error_message_ident,
|
||||
#expr_string,
|
||||
#new_expr,
|
||||
);
|
||||
});
|
||||
new_expr
|
||||
}
|
||||
|
||||
// Defines a variable for each argument expression so that it's evaluated
|
||||
// exactly once.
|
||||
fn define_variables_for_args(
|
||||
&mut self,
|
||||
args: Punctuated<Expr, Comma>,
|
||||
) -> Punctuated<Expr, Comma> {
|
||||
args.into_pairs()
|
||||
.map(|mut pair| {
|
||||
// Don't need to assign literals to intermediate variables.
|
||||
if is_literal(pair.value()) {
|
||||
return pair;
|
||||
}
|
||||
|
||||
let var_expr = self.define_variable(pair.value());
|
||||
let error_message_ident = &self.error_message_ident;
|
||||
let expr_string = expr_to_string(pair.value());
|
||||
self.statements.push(quote! {
|
||||
::googletest::fmt::internal::__googletest__write_expr_value!(
|
||||
&mut #error_message_ident,
|
||||
#expr_string,
|
||||
#var_expr,
|
||||
);
|
||||
});
|
||||
|
||||
*pair.value_mut() = var_expr;
|
||||
pair
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Defines a new variable assigned to the expression and returns the
|
||||
/// variable as an expression to be used in place of the passed-in
|
||||
/// expression.
|
||||
fn define_variable(&mut self, value: &Expr) -> Expr {
|
||||
let var_name =
|
||||
Ident::new(&format!("__googletest__verify_pred__var{}", self.var_num), value.span());
|
||||
self.var_num += 1;
|
||||
self.statements.push(quote! {
|
||||
#[allow(non_snake_case)]
|
||||
let mut #var_name = #value;
|
||||
});
|
||||
syn::parse::<Expr>(quote!(#var_name).into()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
// Whether it's a literal or unary operator applied to a literal (1, -1).
|
||||
fn is_literal(expr: &Expr) -> bool {
|
||||
match expr {
|
||||
Expr::Lit(_) => true,
|
||||
Expr::Unary(unary) => matches!(&*unary.expr, Expr::Lit(_)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_pred_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let parsed = parse_macro_input!(input as Expr);
|
||||
let error_message = quote!(#parsed).to_string() + " was false with";
|
||||
|
||||
let mut state = AccumulatePartsState::new();
|
||||
let pred_value = state.accumulate_parts(parsed);
|
||||
let AccumulatePartsState { error_message_ident, mut statements, .. } = state;
|
||||
|
||||
let _ = statements.pop(); // The last statement prints the full expression itself.
|
||||
quote! {
|
||||
{
|
||||
let mut #error_message_ident = #error_message.to_string();
|
||||
#(#statements)*
|
||||
if (#pred_value) {
|
||||
Ok(())
|
||||
} else {
|
||||
::core::result::Result::Err(
|
||||
::googletest::internal::test_outcome::TestAssertionFailure::create(
|
||||
#error_message_ident))
|
||||
}
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
Reference in new issue
Block a user