├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── ci ├── azure-install-rust.yml ├── azure.yml ├── docker │ └── x86_64-unknown-linux-gnu │ │ └── Dockerfile ├── run-docker.sh └── run.sh ├── src └── lib.rs └── testcrate ├── Cargo.toml ├── build.rs ├── src ├── bin │ ├── t1.rs │ ├── t1_cxx.rs │ ├── t2.rs │ └── t2_cxx.rs ├── lib.rs ├── t1.c ├── t1.cpp ├── t1.h ├── t1.rs ├── t2.c ├── t2.cpp ├── t2.h └── t2.rs └── tests └── all.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ctest" 3 | version = "0.2.22" 4 | authors = [ 5 | "Alex Crichton ", 6 | "Gonzalo Brito Gadeschi " 7 | ] 8 | license = "MIT/Apache-2.0" 9 | readme = "README.md" 10 | repository = "https://github.com/gnzlbg/ctest" 11 | homepage = "https://github.com/gnzlbg/ctest" 12 | documentation = "https://docs.rs/ctest" 13 | description = """ 14 | Automated tests of FFI bindings. 15 | """ 16 | 17 | [dependencies] 18 | syntex_syntax2 = "0.0.2" 19 | cc = "1.0.1" 20 | rustc_version = "0.2" 21 | 22 | [workspace] 23 | members = ["testcrate"] 24 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Alex Crichton 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ctest 2 | 3 | [![Build Status](https://dev.azure.com/gonzalobg88/ctest/_apis/build/status/gnzlbg.ctest?branchName=master)](https://dev.azure.com/gonzalobg88/ctest/_build/latest?definitionId=5&branchName=master) 4 | [Documentation][dox] 5 | 6 | [dox]: https://docs.rs/ctest 7 | 8 | Automated testing of FFI bindings in Rust. This repository is intended to 9 | validate the `*-sys` crates that can be found on crates.io to ensure that the 10 | APIs in Rust match the APIs defined in C. 11 | 12 | ### Example 13 | 14 | Unfortunately the usage today is a little wonky, but to use this library, first, 15 | create a new Cargo project in your repo: 16 | 17 | ``` 18 | $ cargo new --bin systest 19 | ``` 20 | 21 | Then, edit `systest/Cargo.toml` to add these dependencies: 22 | 23 | ```toml 24 | [package] 25 | # ... 26 | build = "build.rs" 27 | 28 | [dependencies] 29 | mylib-sys = { path = "../mylib-sys" } 30 | libc = "0.2" 31 | 32 | [build-dependencies] 33 | ctest = "0.2" 34 | ``` 35 | 36 | Next, add a build script to `systest/build.rs`: 37 | 38 | ```rust 39 | extern crate ctest; 40 | 41 | fn main() { 42 | let mut cfg = ctest::TestGenerator::new(); 43 | 44 | // Include the header files where the C APIs are defined 45 | cfg.header("foo.h") 46 | .header("bar.h"); 47 | 48 | // Include the directory where the header files are defined 49 | cfg.include("path/to/include"); 50 | 51 | // Generate the tests, passing the path to the `*-sys` library as well as 52 | // the module to generate. 53 | cfg.generate("../mylib-sys/lib.rs", "all.rs"); 54 | } 55 | 56 | ``` 57 | 58 | Next, add this to `src/main.rs` 59 | 60 | ```rust 61 | #![allow(bad_style)] 62 | 63 | extern crate mylib_sys; 64 | extern crate libc; 65 | 66 | use libc::*; 67 | use mylib_sys::*; 68 | 69 | include!(concat!(env!("OUT_DIR"), "/all.rs")); 70 | ``` 71 | 72 | And you're good to go! To run the tests execute `cargo run` in the `systest` 73 | directory, and everything should be kicked into action! 74 | 75 | ### How it works 76 | 77 | This library will parse the `*-sys` crate to learn about all extern fn 78 | definitions within. It will then generate a test suite to ensure that all 79 | function function signatures, constant values, struct layout/alignment, type 80 | size/alignment, etc, all match their C equivalent. 81 | 82 | The generated tests come in two forms. One is a Rust file which contains the 83 | `main` function (hence the `include!` above), and another is a C file which is 84 | compiled as part of the build script. The C file is what includes all headers 85 | and returns information about the C side of things (which is validated in Rust). 86 | 87 | A large amount of configuration can be applied to how the C file is generated, 88 | you can browse [the documentation][dox]. 89 | 90 | ### Projects using ctest 91 | 92 | * [libc](https://github.com/rust-lang/libc) 93 | * [git2-rs](https://github.com/rust-lang/git2-rs) 94 | * [ssh2-rs](https://github.com/alexcrichton/ssh2-rs) 95 | * [libz-sys](https://github.com/rust-lang/libz-sys) 96 | * [openssl-sys](https://github.com/sfackler/rust-openssl) 97 | 98 | ### License 99 | 100 | This project is licensed under either of 101 | 102 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 103 | http://www.apache.org/licenses/LICENSE-2.0) 104 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 105 | http://opensource.org/licenses/MIT) 106 | 107 | at your option. 108 | 109 | ### Contribution 110 | 111 | Unless you explicitly state otherwise, any contribution intentionally submitted 112 | for inclusion in ctest by you, as defined in the Apache-2.0 license, shall be 113 | dual licensed as above, without any additional terms or conditions. 114 | -------------------------------------------------------------------------------- /ci/azure-install-rust.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - bash: | 3 | set -ex 4 | toolchain=$TOOLCHAIN 5 | if [ "$toolchain" = "" ]; then 6 | toolchain=nightly 7 | fi 8 | if command -v rustup; then 9 | rustup update $toolchain 10 | rustup default $toolchain 11 | else 12 | curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $toolchain 13 | echo "##vso[task.prependpath]$HOME/.cargo/bin" 14 | fi 15 | displayName: Install rust (unix) 16 | condition: ne( variables['Agent.OS'], 'Windows_NT' ) 17 | - script: | 18 | @echo on 19 | if not defined TOOLCHAIN set TOOLCHAIN=nightly 20 | rustup update --no-self-update %TOOLCHAIN%-%TARGET% 21 | rustup default %TOOLCHAIN%-%TARGET% 22 | displayName: Install rust (windows) 23 | condition: eq( variables['Agent.OS'], 'Windows_NT' ) 24 | - script: | 25 | set -ex 26 | if [ -n "${TARGET}" ]; then 27 | rustup target add $TARGET 28 | fi 29 | condition: ne( variables['Agent.OS'], 'Windows_NT' ) 30 | displayName: Install target (unix) 31 | - script: | 32 | @echo on 33 | if defined TARGET rustup target add %TARGET% 34 | condition: eq( variables['Agent.OS'], 'Windows_NT' ) 35 | displayName: Install target (windows) 36 | - script: | 37 | @echo on 38 | if "%ARCH%" == "i686" choco install mingw --x86 --force 39 | condition: eq( variables['Agent.OS'], 'Windows_NT' ) 40 | displayName: Install MinGW32 (windows) 41 | - bash: | 42 | set -ex 43 | gcc -print-search-dirs 44 | find "C:\ProgramData\Chocolatey" -name "crt2*" 45 | find "C:\ProgramData\Chocolatey" -name "dllcrt2*" 46 | find "C:\ProgramData\Chocolatey" -name "libmsvcrt*" 47 | condition: eq( variables['Agent.OS'], 'Windows_NT' ) 48 | displayName: Find GCC libraries (windows) 49 | - bash: | 50 | set -ex 51 | if [[ -n ${ARCH_BITS} ]]; then 52 | for i in crt2.o dllcrt2.o libmingwex.a libmsvcrt.a ; do 53 | cp -f "/C/ProgramData/Chocolatey/lib/mingw/tools/install/mingw${ARCH_BITS}/${ARCH}-w64-mingw32/lib/$i" "`rustc --print sysroot`/lib/rustlib/${TARGET}/lib" 54 | done 55 | fi 56 | condition: eq( variables['Agent.OS'], 'Windows_NT' ) 57 | displayName: Fix MinGW (windows) 58 | - bash: | 59 | set -ex 60 | rustc -Vv 61 | cargo -V 62 | rustup -Vv 63 | rustup show 64 | which rustc 65 | which cargo 66 | which rustup 67 | displayName: Query rust and cargo versions 68 | - script: | 69 | @echo on 70 | where gcc 71 | condition: eq( variables['Agent.OS'], 'Windows_NT' ) 72 | displayName: Query gcc path 73 | - bash: | 74 | set -ex 75 | cargo generate-lockfile 76 | displayName: Generate lockfiles 77 | 78 | -------------------------------------------------------------------------------- /ci/azure.yml: -------------------------------------------------------------------------------- 1 | pr: ["master"] 2 | 3 | jobs: 4 | - job: NightlyLinux 5 | pool: 6 | vmImage: ubuntu-16.04 7 | steps: 8 | - template: azure-install-rust.yml 9 | - bash: cargo test --all 10 | displayName: Run tests 11 | - bash: sh ./ci/run-docker.sh $TARGET 12 | displayName: Run libc tests 13 | - bash: | 14 | if rustup component add rustfmt-preview ; then 15 | which rustfmt 16 | rustfmt -V 17 | cargo fmt --all -- --check 18 | fi 19 | displayName: rustfmt 20 | - bash: | 21 | if rustup component add clippy-preview ; then 22 | cargo clippy -- -D clippy::pedantic 23 | fi 24 | displayName: clippy 25 | # FIXME: shellcheck 26 | # - bash: | 27 | # if shellcheck --version ; then 28 | # shellcheck -e SC2103 ci/*.sh 29 | # else 30 | # echo "shellcheck not found" 31 | # exit 1 32 | # fi 33 | # displayName: shellcheck 34 | strategy: 35 | matrix: 36 | x86_64-unknown-linux-gnu: 37 | TARGET: x86_64-unknown-linux-gnu 38 | 39 | - job: OSX64 40 | pool: 41 | vmImage: macos-10.14 42 | steps: 43 | - template: azure-install-rust.yml 44 | - bash: cargo test --all 45 | displayName: Run tests 46 | - bash: sh ./ci/run.sh $TARGET 47 | displayName: Run libc tests 48 | strategy: 49 | matrix: 50 | x86_64-apple-darwin: 51 | TARGET: x86_64-apple-darwin 52 | 53 | - job: OSX32 54 | pool: 55 | vmImage: macos-10.13 56 | steps: 57 | - template: azure-install-rust.yml 58 | - bash: cargo test --all 59 | displayName: Run tests 60 | - bash: sh ./ci/run.sh $TARGET 61 | displayName: Run libc tests 62 | strategy: 63 | matrix: 64 | i686-apple-darwin: 65 | TARGET: i686-apple-darwin 66 | 67 | - job: Windows 68 | pool: 69 | vmImage: vs2017-win2016 70 | steps: 71 | - template: azure-install-rust.yml 72 | - bash: cargo test --all 73 | displayName: Test 74 | strategy: 75 | matrix: 76 | x86_64-pc-windows-gnu: 77 | TARGET: x86_64-pc-windows-gnu 78 | ARCH_BITS: 64 79 | ARCH: x86_64 80 | x86_64-pc-windows-msvc: 81 | TARGET: x86_64-pc-windows-msvc 82 | i686-pc-windows-gnu: 83 | TARGET: i686-pc-windows-gnu 84 | ARCH_BITS: 32 85 | ARCH: i686 86 | i686-pc-windows-msvc: 87 | TARGET: i686-pc-windows-msvc 88 | 89 | - job: StabeBeta 90 | pool: 91 | vmImage: macos-10.14 92 | steps: 93 | - template: azure-install-rust.yml 94 | - bash: cargo test --all 95 | displayName: Test 96 | strategy: 97 | matrix: 98 | stable: 99 | TARGET: x86_64-apple-darwin 100 | TOOLCHAIN: stable 101 | beta: 102 | TARGET: x86_64-apple-darwin 103 | TOOLCHAIN: beta 104 | -------------------------------------------------------------------------------- /ci/docker/x86_64-unknown-linux-gnu/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:19.04 2 | RUN apt-get update 3 | RUN apt-get install -y --no-install-recommends \ 4 | gcc libc6-dev ca-certificates linux-headers-generic git 5 | 6 | RUN apt search linux-headers 7 | RUN ls /usr/src 8 | 9 | ENV PATH=$PATH:/rust/bin 10 | -------------------------------------------------------------------------------- /ci/run-docker.sh: -------------------------------------------------------------------------------- 1 | # Small script to run tests for a target (or all targets) inside all the 2 | # respective docker images. 3 | 4 | set -ex 5 | 6 | run() { 7 | echo "Building docker container for TARGET=${1}" 8 | docker build -t ctest -f ci/docker/$1/Dockerfile ci/ 9 | mkdir -p target 10 | target=$1 11 | echo "Running docker" 12 | docker run \ 13 | --user `id -u`:`id -g` \ 14 | --rm \ 15 | --init \ 16 | --volume $HOME/.cargo:/cargo-h \ 17 | --env CARGO_HOME=/cargo-h \ 18 | --volume `rustc --print sysroot`:/rust:ro \ 19 | --env TARGET=$target \ 20 | --volume `pwd`:/checkout:ro \ 21 | --volume `pwd`/target:/checkout/target \ 22 | --workdir /checkout \ 23 | --privileged \ 24 | ctest \ 25 | bash \ 26 | -c 'PATH=/rust/bin:$PATH exec ci/run.sh' 27 | } 28 | 29 | if [ -z "$1" ]; then 30 | for d in `ls ci/docker/`; do 31 | run $d 32 | done 33 | else 34 | run $1 35 | fi 36 | -------------------------------------------------------------------------------- /ci/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Builds and runs tests for a particular target passed as an argument to this 4 | # script. 5 | 6 | set -ex 7 | 8 | : ${TARGET?"The TARGET environment variable must be set."} 9 | 10 | mkdir -p target 11 | rm -rf target/libc || true 12 | git clone --depth=1 https://github.com/rust-lang/libc target/libc 13 | mkdir -p target/libc/target/ctest 14 | 15 | case $TARGET in 16 | *linux*) 17 | sed -i 's@ctest = "0.2"@ctest = { path = "../../.." }@g' target/libc/libc-test/Cargo.toml 18 | ;; 19 | *apple*) 20 | sed -i '' 's@ctest = "0.2"@ctest = { path = "../../.." }@g' target/libc/libc-test/Cargo.toml 21 | ;; 22 | esac 23 | 24 | cargo test --release --manifest-path target/libc/libc-test/Cargo.toml --target $TARGET 25 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # ctest - an FFI binding validator 2 | //! 3 | //! This library is intended to be used as a build dependency in a separate 4 | //! project from the main repo to generate tests which can be used to validate 5 | //! FFI bindings in Rust against the headers from which they come from. 6 | //! 7 | //! For example usage, see the [main `README.md`][project] for how to set it 8 | //! up. 9 | //! 10 | //! [project]: https://github.com/alexcrichton/ctest 11 | 12 | #![deny(missing_docs)] 13 | #![allow(bare_trait_objects)] 14 | 15 | extern crate cc; 16 | extern crate syntex_syntax2 as syntax; 17 | 18 | extern crate rustc_version; 19 | 20 | use std::cell::RefCell; 21 | use std::collections::{HashMap, HashSet}; 22 | use std::env; 23 | use std::fs::File; 24 | use std::io::prelude::*; 25 | use std::io::BufWriter; 26 | use std::path::{Path, PathBuf}; 27 | use std::rc::Rc; 28 | 29 | use syntax::abi::Abi; 30 | use syntax::ast; 31 | use syntax::ast::Attribute; 32 | use syntax::ast::Name; 33 | use syntax::attr::{self, ReprAttr}; 34 | use syntax::codemap::FilePathMapping; 35 | use syntax::config::StripUnconfigured; 36 | use syntax::errors::Handler as SpanHandler; 37 | use syntax::ext::base::{Determinacy, ExtCtxt, MacroKind, Resolver, SyntaxExtension}; 38 | use syntax::ext::expand::{Expansion, ExpansionConfig, Invocation, InvocationKind}; 39 | use syntax::ext::hygiene::Mark; 40 | use syntax::ext::tt::macro_rules; 41 | use syntax::feature_gate::Features; 42 | use syntax::fold::{self, Folder}; 43 | use syntax::parse::{self, ParseSess}; 44 | use syntax::ptr::P; 45 | use syntax::util::small_vector::SmallVector; 46 | use syntax::visit::{self, Visitor}; 47 | 48 | macro_rules! t { 49 | ($e:expr) => { 50 | match $e { 51 | Ok(e) => e, 52 | Err(e) => panic!("{} failed with {}", stringify!($e), e), 53 | } 54 | }; 55 | } 56 | 57 | /// Programming language 58 | #[derive(Debug)] 59 | pub enum Lang { 60 | /// The C programming language. 61 | C, 62 | /// The C++ programming language. 63 | CXX, 64 | } 65 | 66 | /// A kind of item to which the C volatile qualifier could apply. 67 | #[derive(Debug)] 68 | pub enum VolatileItemKind { 69 | /// A struct field (struct_name, field_name) 70 | StructField(String, String), 71 | /// An extern static 72 | Static(String), 73 | /// N-th function argument 74 | FunctionArg(String, usize), 75 | /// Function return type 76 | FunctionRet(String), 77 | #[doc(hidden)] 78 | __Other, 79 | } 80 | 81 | /// A builder used to generate a test suite. 82 | /// 83 | /// This builder has a number of configuration options which modify how the 84 | /// generated tests are emitted, and it is also the main entry point for parsing 85 | /// an FFI header crate for definitions. 86 | pub struct TestGenerator { 87 | headers: Vec, 88 | includes: Vec, 89 | lang: Lang, 90 | flags: Vec, 91 | target: Option, 92 | out_dir: Option, 93 | defines: Vec<(String, Option)>, 94 | cfg: Vec<(String, Option)>, 95 | verbose_skip: bool, 96 | volatile_item: Box bool>, 97 | array_arg: Box bool>, 98 | skip_fn: Box bool>, 99 | skip_fn_ptrcheck: Box bool>, 100 | skip_static: Box bool>, 101 | skip_field: Box bool>, 102 | skip_field_type: Box bool>, 103 | skip_const: Box bool>, 104 | skip_signededness: Box bool>, 105 | skip_type: Box bool>, 106 | skip_struct: Box bool>, 107 | skip_roundtrip: Box bool>, 108 | field_name: Box String>, 109 | type_name: Box String>, 110 | fn_cname: Box) -> String>, 111 | const_cname: Box String>, 112 | rust_version: rustc_version::Version, 113 | } 114 | 115 | struct TyFinder { 116 | structs: HashSet, 117 | unions: HashSet, 118 | aliases: HashMap>, 119 | } 120 | 121 | struct Generator<'a> { 122 | target: &'a str, 123 | rust: Box, 124 | c: Box, 125 | sh: &'a SpanHandler, 126 | structs: HashSet, 127 | unions: HashSet, 128 | aliases: HashMap>, 129 | files: HashSet, 130 | abi: Abi, 131 | tests: Vec, 132 | sess: &'a ParseSess, 133 | opts: &'a TestGenerator, 134 | } 135 | 136 | impl TestGenerator { 137 | /// Creates a new blank test generator. 138 | /// 139 | /// This won't actually be that useful until functions like `header` are 140 | /// called, but the main "finalization method" is the `generate` method. 141 | pub fn new() -> Self { 142 | Self { 143 | headers: Vec::new(), 144 | includes: Vec::new(), 145 | lang: Lang::C, 146 | flags: Vec::new(), 147 | target: None, 148 | out_dir: None, 149 | defines: Vec::new(), 150 | cfg: Vec::new(), 151 | verbose_skip: false, 152 | volatile_item: Box::new(|_| false), 153 | array_arg: Box::new(|_, _| false), 154 | skip_fn: Box::new(|_| false), 155 | skip_fn_ptrcheck: Box::new(|_| false), 156 | skip_static: Box::new(|_| false), 157 | skip_const: Box::new(|_| false), 158 | skip_signededness: Box::new(|_| false), 159 | skip_type: Box::new(|_| false), 160 | skip_struct: Box::new(|_| false), 161 | skip_roundtrip: Box::new(|_| false), 162 | field_name: Box::new(|_, f| f.to_string()), 163 | skip_field: Box::new(|_, _| false), 164 | skip_field_type: Box::new(|_, _| false), 165 | fn_cname: Box::new(|a, _| a.to_string()), 166 | type_name: Box::new(|f, is_struct, is_union| { 167 | if is_struct { 168 | format!("struct {}", f) 169 | } else if is_union { 170 | format!("union {}", f) 171 | } else { 172 | f.to_string() 173 | } 174 | }), 175 | const_cname: Box::new(std::string::ToString::to_string), 176 | rust_version: rustc_version::version().unwrap(), 177 | } 178 | } 179 | 180 | /// Add a header to be included as part of the generated C file. 181 | /// 182 | /// The generate C test will be compiled by a C compiler, and this can be 183 | /// used to ensure that all the necessary header files are included to test 184 | /// all FFI definitions. 185 | /// 186 | /// # Examples 187 | /// 188 | /// ```no_run 189 | /// use std::env; 190 | /// use std::path::PathBuf; 191 | /// 192 | /// use ctest::TestGenerator; 193 | /// 194 | /// let mut cfg = TestGenerator::new(); 195 | /// cfg.header("foo.h") 196 | /// .header("bar.h"); 197 | /// ``` 198 | pub fn header(&mut self, header: &str) -> &mut Self { 199 | self.headers.push(header.to_string()); 200 | self 201 | } 202 | 203 | /// Target Rust version: `major`.`minor`.`patch` 204 | /// 205 | /// # Examples 206 | /// 207 | /// ```no_run 208 | /// use ctest::TestGenerator; 209 | /// 210 | /// let mut cfg = TestGenerator::new(); 211 | /// cfg.rust_version(1, 0, 1); 212 | /// ``` 213 | pub fn rust_version(&mut self, major: u64, minor: u64, patch: u64) -> &mut Self { 214 | self.rust_version = rustc_version::Version::new(major, minor, patch); 215 | self 216 | } 217 | 218 | /// Add a path to the C compiler header lookup path. 219 | /// 220 | /// This is useful for if the C library is installed to a nonstandard 221 | /// location to ensure that compiling the C file succeeds. 222 | /// 223 | /// # Examples 224 | /// 225 | /// ```no_run 226 | /// use std::env; 227 | /// use std::path::PathBuf; 228 | /// 229 | /// use ctest::TestGenerator; 230 | /// 231 | /// let mut cfg = TestGenerator::new(); 232 | /// let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); 233 | /// cfg.include(out_dir.join("include")); 234 | /// ``` 235 | pub fn include>(&mut self, p: P) -> &mut Self { 236 | self.includes.push(p.as_ref().to_owned()); 237 | self 238 | } 239 | 240 | /// Sets the programming language. 241 | /// 242 | /// # Examples 243 | /// 244 | /// ```no_run 245 | /// use std::env; 246 | /// use std::path::PathBuf; 247 | /// 248 | /// use ctest::{TestGenerator, Lang}; 249 | /// 250 | /// let mut cfg = TestGenerator::new(); 251 | /// cfg.language(Lang::CXX); 252 | /// ``` 253 | pub fn language(&mut self, lang: Lang) -> &mut Self { 254 | self.lang = lang; 255 | self 256 | } 257 | 258 | /// Add a flag to the C compiler invocation. 259 | /// 260 | /// This can be useful for tweaking the warning settings of the underlying 261 | /// compiler. 262 | /// 263 | /// # Examples 264 | /// 265 | /// ```no_run 266 | /// use std::env; 267 | /// use std::path::PathBuf; 268 | /// 269 | /// use ctest::TestGenerator; 270 | /// 271 | /// let mut cfg = TestGenerator::new(); 272 | /// 273 | /// // if msvc 274 | /// cfg.flag("/wd4820"); 275 | /// 276 | /// // if gnu 277 | /// cfg.flag("-Wno-type-limits"); 278 | /// ``` 279 | pub fn flag(&mut self, flag: &str) -> &mut Self { 280 | self.flags.push(flag.to_string()); 281 | self 282 | } 283 | 284 | /// Configures the output directory of the generated Rust and C code. 285 | /// 286 | /// Note that for Cargo builds this defaults to `$OUT_DIR` and it's not 287 | /// necessary to call. 288 | /// 289 | /// # Examples 290 | /// 291 | /// ```no_run 292 | /// use ctest::TestGenerator; 293 | /// 294 | /// let mut cfg = TestGenerator::new(); 295 | /// cfg.out_dir("path/to/output"); 296 | /// ``` 297 | pub fn out_dir>(&mut self, p: P) -> &mut Self { 298 | self.out_dir = Some(p.as_ref().to_owned()); 299 | self 300 | } 301 | 302 | /// Configures the target to compile C code for. 303 | /// 304 | /// Note that for Cargo builds this defaults to `$TARGET` and it's not 305 | /// necessary to call. 306 | /// 307 | /// # Examples 308 | /// 309 | /// ```no_run 310 | /// use ctest::TestGenerator; 311 | /// 312 | /// let mut cfg = TestGenerator::new(); 313 | /// cfg.target("x86_64-unknown-linux-gnu"); 314 | /// ``` 315 | pub fn target(&mut self, target: &str) -> &mut Self { 316 | self.target = Some(target.to_string()); 317 | self 318 | } 319 | 320 | /// Set a `-D` flag for the C compiler being called. 321 | /// 322 | /// This can be used to define various variables to configure how header 323 | /// files are included or what APIs are exposed from header files. 324 | /// 325 | /// # Examples 326 | /// 327 | /// ```no_run 328 | /// use ctest::TestGenerator; 329 | /// 330 | /// let mut cfg = TestGenerator::new(); 331 | /// cfg.define("_GNU_SOURCE", None) 332 | /// .define("_WIN32_WINNT", Some("0x8000")); 333 | /// ``` 334 | pub fn define(&mut self, k: &str, v: Option<&str>) -> &mut Self { 335 | self.defines 336 | .push((k.to_string(), v.map(std::string::ToString::to_string))); 337 | self 338 | } 339 | 340 | /// Set a `--cfg` option with which to expand the Rust FFI crate. 341 | /// 342 | /// By default the Rust code is run through expansion to determine what C 343 | /// APIs are exposed (to allow differences across platforms). 344 | /// 345 | /// The `k` argument is the `#[cfg]` value to define, while `v` is the 346 | /// optional value of `v`: 347 | /// 348 | /// * `k == "foo"` and `v == None` makes `#[cfg(foo)]` expand. That is, 349 | /// `cfg!(foo)` expands to `true`. 350 | /// 351 | /// * `k == "bar"` and `v == Some("baz")` makes `#[cfg(bar = "baz")]` 352 | /// expand. That is, `cfg!(bar = "baz")` expands to `true`. 353 | /// 354 | /// # Examples 355 | /// 356 | /// ```no_run 357 | /// use ctest::TestGenerator; 358 | /// 359 | /// let mut cfg = TestGenerator::new(); 360 | /// cfg.cfg("foo", None) // cfg!(foo) 361 | /// .cfg("bar", Some("baz")); // cfg!(bar = "baz") 362 | /// ``` 363 | pub fn cfg(&mut self, k: &str, v: Option<&str>) -> &mut Self { 364 | self.cfg 365 | .push((k.to_string(), v.map(std::string::ToString::to_string))); 366 | self 367 | } 368 | 369 | /// Skipped item names are printed to `stderr` if `v` is `true`. 370 | pub fn verbose_skip(&mut self, v: bool) -> &mut Self { 371 | self.verbose_skip = v; 372 | self 373 | } 374 | 375 | /// Configures how a Rust type name is translated to a C type name. 376 | /// 377 | /// The closure is given a Rust type name as well as a boolean indicating 378 | /// wehther it's a struct or not. 379 | /// 380 | /// The default behavior is that `struct foo` in Rust is translated to 381 | /// `struct foo` in C, and `type foo` in Rust is translated to `foo` in C. 382 | /// Some header files, however, have the convention that `struct foo_t` in 383 | /// Rust should be `foo_t` in C, for example. 384 | /// 385 | /// # Examples 386 | /// 387 | /// ```no_run 388 | /// use ctest::TestGenerator; 389 | /// 390 | /// let mut cfg = TestGenerator::new(); 391 | /// cfg.type_name(|ty, is_struct, is_union| { 392 | /// if is_struct { 393 | /// format!("{}_t", ty) 394 | /// } else { 395 | /// ty.to_string() 396 | /// } 397 | /// }); 398 | pub fn type_name(&mut self, f: F) -> &mut Self 399 | where 400 | F: Fn(&str, bool, bool) -> String + 'static, 401 | { 402 | self.type_name = Box::new(f); 403 | self 404 | } 405 | 406 | /// Configures how a Rust struct field is translated to a C struct field. 407 | /// 408 | /// The closure is given a Rust struct name as well as a field within that 409 | /// struct. The name of the corresponding field in C is then returned. 410 | /// 411 | /// By default the field name in C just matches the field name in Rust, but 412 | /// this is useful for fields which otherwise are named after keywords in 413 | /// Rust (such as a field name of `type`). 414 | /// 415 | /// # Examples 416 | /// 417 | /// ```no_run 418 | /// use ctest::TestGenerator; 419 | /// 420 | /// let mut cfg = TestGenerator::new(); 421 | /// cfg.field_name(|_s, field| { 422 | /// field.replace("foo", "bar") 423 | /// }); 424 | pub fn field_name(&mut self, f: F) -> &mut Self 425 | where 426 | F: Fn(&str, &str) -> String + 'static, 427 | { 428 | self.field_name = Box::new(f); 429 | self 430 | } 431 | 432 | /// Is volatile? 433 | /// 434 | /// The closure given takes a `VolatileKind` denoting a particular item that 435 | /// could be volatile, and returns whether this is the case. 436 | /// 437 | /// # Examples 438 | /// 439 | /// ```no_run 440 | /// use ctest::{TestGenerator, VolatileItemKind::StructField}; 441 | /// 442 | /// let mut cfg = TestGenerator::new(); 443 | /// cfg.volatile_item(|i| { 444 | /// match i { 445 | /// StructField(ref s, ref f) 446 | /// if s == "foo_struct" && f == "foo_field" 447 | /// => true, 448 | /// _ => false, 449 | /// }}); 450 | /// ``` 451 | pub fn volatile_item(&mut self, f: F) -> &mut Self 452 | where 453 | F: Fn(VolatileItemKind) -> bool + 'static, 454 | { 455 | self.volatile_item = Box::new(f); 456 | self 457 | } 458 | 459 | /// Is argument of function an array? 460 | /// 461 | /// The closure denotes whether particular argument of a function is an array. 462 | /// 463 | /// # Examples 464 | /// 465 | /// ```no_run 466 | /// use ctest::{TestGenerator}; 467 | /// 468 | /// let mut cfg = TestGenerator::new(); 469 | /// cfg.array_arg(|i, n| { 470 | /// match (i, n) { 471 | /// ("foo", 0) => true, 472 | /// _ => false, 473 | /// }}); 474 | /// ``` 475 | pub fn array_arg(&mut self, f: F) -> &mut Self 476 | where 477 | F: Fn(&str, usize) -> bool + 'static, 478 | { 479 | self.array_arg = Box::new(f); 480 | self 481 | } 482 | 483 | /// Configures how Rust `const`s names are translated to C. 484 | /// 485 | /// The closure is given a Rust `const` name. The name of the corresponding 486 | /// `const` in C is then returned. 487 | /// 488 | /// By default the `const` name in C just matches the `const` name in Rust. 489 | /// 490 | /// # Examples 491 | /// 492 | /// ```no_run 493 | /// use ctest::TestGenerator; 494 | /// 495 | /// let mut cfg = TestGenerator::new(); 496 | /// cfg.const_cname(|c| { 497 | /// c.replace("FOO", "foo") 498 | /// }); 499 | pub fn const_cname(&mut self, f: F) -> &mut Self 500 | where 501 | F: Fn(&str) -> String + 'static, 502 | { 503 | self.const_cname = Box::new(f); 504 | self 505 | } 506 | 507 | /// Configures whether all tests for a field are skipped or not. 508 | /// 509 | /// The closure is given a Rust struct name as well as a field within that 510 | /// struct. A flag indicating whether the field should be tested for type, 511 | /// size, offset, and alignment should be skipped or not. 512 | /// 513 | /// By default all field properties are tested. 514 | /// 515 | /// # Examples 516 | /// 517 | /// ```no_run 518 | /// use ctest::TestGenerator; 519 | /// 520 | /// let mut cfg = TestGenerator::new(); 521 | /// cfg.skip_field(|s, field| { 522 | /// s == "foo_t" || (s == "bar_t" && field == "bar") 523 | /// }); 524 | pub fn skip_field(&mut self, f: F) -> &mut Self 525 | where 526 | F: Fn(&str, &str) -> bool + 'static, 527 | { 528 | self.skip_field = Box::new(f); 529 | self 530 | } 531 | 532 | /// Configures whether tests for the type of a field is skipped or not. 533 | /// 534 | /// The closure is given a Rust struct name as well as a field within that 535 | /// struct. A flag indicating whether the field's type should be tested is 536 | /// returned. 537 | /// 538 | /// By default all field properties are tested. 539 | /// 540 | /// # Examples 541 | /// 542 | /// ```no_run 543 | /// use ctest::TestGenerator; 544 | /// 545 | /// let mut cfg = TestGenerator::new(); 546 | /// cfg.skip_field_type(|s, field| { 547 | /// s == "foo_t" || (s == "bar_t" && field == "bar") 548 | /// }); 549 | /// ``` 550 | pub fn skip_field_type(&mut self, f: F) -> &mut Self 551 | where 552 | F: Fn(&str, &str) -> bool + 'static, 553 | { 554 | self.skip_field_type = Box::new(f); 555 | self 556 | } 557 | 558 | /// Configures whether a types signededness is tested or not. 559 | /// 560 | /// The closure is given the name of a Rust type, and returns whether the 561 | /// type should be tested as having the right sign (positive or negative). 562 | /// 563 | /// By default all signededness checks are performed. 564 | /// 565 | /// # Examples 566 | /// 567 | /// ```no_run 568 | /// use ctest::TestGenerator; 569 | /// 570 | /// let mut cfg = TestGenerator::new(); 571 | /// cfg.skip_signededness(|s| { 572 | /// s.starts_with("foo_") 573 | /// }); 574 | /// ``` 575 | pub fn skip_signededness(&mut self, f: F) -> &mut Self 576 | where 577 | F: Fn(&str) -> bool + 'static, 578 | { 579 | self.skip_signededness = Box::new(f); 580 | self 581 | } 582 | 583 | /// Configures whether tests for a function definition are generated. 584 | /// 585 | /// The closure is given the name of a Rust FFI function and returns whether 586 | /// test will be generated. 587 | /// 588 | /// By default, a function's signature is checked along with its address in 589 | /// memory. 590 | /// 591 | /// # Examples 592 | /// 593 | /// ```no_run 594 | /// use ctest::TestGenerator; 595 | /// 596 | /// let mut cfg = TestGenerator::new(); 597 | /// cfg.skip_fn(|s| { 598 | /// s.starts_with("foo_") 599 | /// }); 600 | /// ``` 601 | pub fn skip_fn(&mut self, f: F) -> &mut Self 602 | where 603 | F: Fn(&str) -> bool + 'static, 604 | { 605 | self.skip_fn = Box::new(f); 606 | self 607 | } 608 | 609 | /// Configures whether tests for a static definition are generated. 610 | /// 611 | /// The closure is given the name of a Rust extern static definition and 612 | /// returns whether test will be generated. 613 | /// 614 | /// By default, a static's type is checked along with its address in 615 | /// memory. 616 | /// 617 | /// # Examples 618 | /// 619 | /// ```no_run 620 | /// use ctest::TestGenerator; 621 | /// 622 | /// let mut cfg = TestGenerator::new(); 623 | /// cfg.skip_static(|s| { 624 | /// s.starts_with("foo_") 625 | /// }); 626 | /// ``` 627 | pub fn skip_static(&mut self, f: F) -> &mut Self 628 | where 629 | F: Fn(&str) -> bool + 'static, 630 | { 631 | self.skip_static = Box::new(f); 632 | self 633 | } 634 | 635 | /// Configures whether tests for a function pointer's value are generated. 636 | /// 637 | /// The closure is given the name of a Rust FFI function and returns whether 638 | /// the test will be generated. 639 | /// 640 | /// By default generated tests will ensure that the function pointer in C 641 | /// corresponds to the same function pointer in Rust. This can often 642 | /// unconver subtle symbol naming issues where a header file is referenced 643 | /// through the C identifier `foo` but the underlying symbol is mapped to 644 | /// something like `__foo_compat`. 645 | pub fn skip_fn_ptrcheck(&mut self, f: F) -> &mut Self 646 | where 647 | F: Fn(&str) -> bool + 'static, 648 | { 649 | self.skip_fn_ptrcheck = Box::new(f); 650 | self 651 | } 652 | 653 | /// Configures whether the tests for a constant's value are generated. 654 | /// 655 | /// The closure is given the name of a Rust constant and returns whether the 656 | /// test will be generated. 657 | /// 658 | /// By default all constant values are verified. 659 | /// 660 | /// # Examples 661 | /// 662 | /// ```no_run 663 | /// use ctest::TestGenerator; 664 | /// 665 | /// let mut cfg = TestGenerator::new(); 666 | /// cfg.skip_const(|s| { 667 | /// s.starts_with("FOO_") 668 | /// }); 669 | /// ``` 670 | pub fn skip_const(&mut self, f: F) -> &mut Self 671 | where 672 | F: Fn(&str) -> bool + 'static, 673 | { 674 | self.skip_const = Box::new(f); 675 | self 676 | } 677 | 678 | /// Configures whether the tests for a typedef are emitted. 679 | /// 680 | /// The closure is passed the name of a Rust typedef and returns whether the 681 | /// tests are generated. 682 | /// 683 | /// By default existence of a typedef is checked. 684 | /// 685 | /// # Examples 686 | /// 687 | /// ```no_run 688 | /// use ctest::TestGenerator; 689 | /// 690 | /// let mut cfg = TestGenerator::new(); 691 | /// cfg.skip_type(|s| { 692 | /// s.starts_with("foo_") 693 | /// }); 694 | /// ``` 695 | pub fn skip_type(&mut self, f: F) -> &mut Self 696 | where 697 | F: Fn(&str) -> bool + 'static, 698 | { 699 | self.skip_type = Box::new(f); 700 | self 701 | } 702 | 703 | /// Configures whether the tests for a struct are emitted. 704 | /// 705 | /// The closure is passed the name of a Rust struct and returns whether the 706 | /// tests are generated. 707 | /// 708 | /// By default structs undergo tests such as size, alignment, existence, 709 | /// field offset, etc. 710 | /// 711 | /// # Examples 712 | /// 713 | /// ```no_run 714 | /// use ctest::TestGenerator; 715 | /// 716 | /// let mut cfg = TestGenerator::new(); 717 | /// cfg.skip_struct(|s| { 718 | /// s.starts_with("foo_") 719 | /// }); 720 | /// ``` 721 | pub fn skip_struct(&mut self, f: F) -> &mut Self 722 | where 723 | F: Fn(&str) -> bool + 'static, 724 | { 725 | self.skip_struct = Box::new(f); 726 | self 727 | } 728 | 729 | /// Configures whether the ABI roundtrip tests for a type are emitted. 730 | /// 731 | /// The closure is passed the name of a Rust type and returns whether the 732 | /// tests are generated. 733 | /// 734 | /// By default all types undergo ABI roundtrip tests. Arrays cannot undergo 735 | /// an ABI roundtrip because they cannot be returned by C functions, and 736 | /// have to be manually skipped here. 737 | /// 738 | /// # Examples 739 | /// 740 | /// ```no_run 741 | /// use ctest::TestGenerator; 742 | /// 743 | /// let mut cfg = TestGenerator::new(); 744 | /// cfg.skip_roundtrip(|s| { 745 | /// s.starts_with("foo_") 746 | /// }); 747 | /// ``` 748 | pub fn skip_roundtrip(&mut self, f: F) -> &mut Self 749 | where 750 | F: Fn(&str) -> bool + 'static, 751 | { 752 | self.skip_roundtrip = Box::new(f); 753 | self 754 | } 755 | 756 | /// Configures the name of a function in the generate C code. 757 | /// 758 | /// The closure is passed the Rust name of a function as well as any 759 | /// optional `#[link_name]` specified. 760 | /// 761 | /// By default the name of the generated C reference is the same as the Rust 762 | /// function. This is useful, however, if different naming conventions are 763 | /// used in Rust than are present in C (which is discouraged, however). 764 | /// 765 | /// # Examples 766 | /// 767 | /// ```no_run 768 | /// use ctest::TestGenerator; 769 | /// 770 | /// let mut cfg = TestGenerator::new(); 771 | /// cfg.fn_cname(|rust, link_name| link_name.unwrap_or(rust).to_string()); 772 | /// ``` 773 | pub fn fn_cname(&mut self, f: F) -> &mut Self 774 | where 775 | F: Fn(&str, Option<&str>) -> String + 'static, 776 | { 777 | self.fn_cname = Box::new(f); 778 | self 779 | } 780 | 781 | /// Generate all tests. 782 | /// 783 | /// This function is first given the path to the `*-sys` crate which is 784 | /// being tested along with an output file from where to generate the Rust 785 | /// side of the tests. 786 | /// 787 | /// This function does not consume the builder, but it is expected that all 788 | /// configuration has happened prior to calling this function. 789 | /// 790 | /// This will also generate the corresponding C side of the tests and 791 | /// compile it. 792 | /// 793 | /// # Examples 794 | /// 795 | /// ```no_run 796 | /// use ctest::TestGenerator; 797 | /// 798 | /// let mut cfg = TestGenerator::new(); 799 | /// cfg.generate("../path/to/libfoo-sys/lib.rs", "all.rs"); 800 | /// ``` 801 | pub fn generate>(&mut self, krate: P, out_file: &str) { 802 | self._generate(krate.as_ref(), out_file) 803 | } 804 | 805 | fn _generate(&mut self, krate: &Path, out_file: &str) { 806 | let out = self.generate_files(krate, out_file); 807 | 808 | let target = self 809 | .target 810 | .clone() 811 | .unwrap_or_else(|| env::var("TARGET").unwrap()); 812 | 813 | // Compile our C shim to be linked into tests 814 | let mut cfg = cc::Build::new(); 815 | if let Lang::CXX = self.lang { 816 | cfg.cpp(true); 817 | } 818 | let ext = match self.lang { 819 | Lang::C => "c", 820 | Lang::CXX => "cpp", 821 | }; 822 | cfg.file(&out.with_extension(ext)); 823 | if target.contains("msvc") { 824 | cfg.flag("/W3").flag("/Wall").flag("/WX") 825 | // ignored warnings 826 | .flag("/wd4820") // warning about adding padding? 827 | .flag("/wd4100") // unused parameters 828 | .flag("/wd4996") // deprecated functions 829 | .flag("/wd4296") // '<' being always false 830 | .flag("/wd4255") // converting () to (void) 831 | .flag("/wd4668") // using an undefined thing in preprocessor? 832 | .flag("/wd4366") // taking ref to packed struct field might be unaligned 833 | .flag("/wd4189") // local variable initialized but not referenced 834 | .flag("/wd4710") // function not inlined 835 | .flag("/wd5045") // compiler will insert Spectre mitigation 836 | .flag("/wd4514") // unreferenced inline function removed 837 | .flag("/wd4711") // function selected for automatic inline 838 | ; 839 | } else { 840 | cfg.flag("-Wall") 841 | .flag("-Wextra") 842 | .flag("-Werror") 843 | .flag("-Wno-unused-parameter") 844 | .flag("-Wno-type-limits") 845 | // allow taking address of packed struct members: 846 | .flag("-Wno-address-of-packed-member") 847 | .flag("-Wno-unknown-warning-option") 848 | .flag("-Wno-deprecated-declarations"); // allow deprecated items 849 | } 850 | 851 | for flag in &self.flags { 852 | cfg.flag(flag); 853 | } 854 | 855 | for &(ref a, ref b) in &self.defines { 856 | cfg.define(a, b.as_ref().map(|s| &s[..])); 857 | } 858 | for p in &self.includes { 859 | cfg.include(p); 860 | } 861 | 862 | let stem = out.file_stem().unwrap().to_str().unwrap(); 863 | cfg.target(&target) 864 | .out_dir(out.parent().unwrap()) 865 | .compile(&format!("lib{}.a", stem)); 866 | } 867 | 868 | #[doc(hidden)] // TODO: needs docs 869 | pub fn generate_files>(&mut self, krate: P, out_file: &str) -> PathBuf { 870 | self._generate_files(krate.as_ref(), out_file) 871 | } 872 | 873 | fn _generate_files(&mut self, krate: &Path, out_file: &str) -> PathBuf { 874 | // Prep the test generator 875 | let out_dir = self 876 | .out_dir 877 | .clone() 878 | .unwrap_or_else(|| PathBuf::from(env::var_os("OUT_DIR").unwrap())); 879 | let out_file = out_dir.join(out_file); 880 | let ext = match self.lang { 881 | Lang::C => "c", 882 | Lang::CXX => "cpp", 883 | }; 884 | let c_file = out_file.with_extension(ext); 885 | let rust_out = BufWriter::new(t!(File::create(&out_file))); 886 | let c_out = BufWriter::new(t!(File::create(&c_file))); 887 | let mut sess = ParseSess::new(FilePathMapping::empty()); 888 | let target = self 889 | .target 890 | .clone() 891 | .unwrap_or_else(|| env::var("TARGET").unwrap()); 892 | for (k, v) in default_cfg(&target).into_iter().chain(self.cfg.clone()) { 893 | let s = |s: &str| ast::Name::intern(s); 894 | sess.config.insert((s(&k), v.as_ref().map(|n| s(n)))); 895 | } 896 | 897 | // Parse the libc crate 898 | let krate = parse::parse_crate_from_file(krate, &sess).ok().unwrap(); 899 | 900 | // Remove things like functions, impls, traits, etc, that we're not 901 | // looking at 902 | let krate = StripUnchecked.fold_crate(krate); 903 | 904 | // expand macros 905 | let features = Features::new(); 906 | let mut ecfg = ExpansionConfig { 907 | features: Some(&features), 908 | ..ExpansionConfig::default("crate_name".to_string()) 909 | }; 910 | ecfg.recursion_limit = 128; 911 | // let exts = vec![ 912 | // (Interner::intern("macro_rules"), SyntaxExtension::MacroRulesTT), 913 | // ]; 914 | println!("-----------------------------------------"); 915 | let mut resolver = MyResolver { 916 | parse_sess: &sess, 917 | map: HashMap::new(), 918 | id: 1_000_000_000, 919 | }; 920 | let mut ecx = ExtCtxt::new(&sess, ecfg, &mut resolver); 921 | let krate = ecx.monotonic_expander().expand_crate(krate); 922 | 923 | // Strip the crate down to just what's configured for our target 924 | let krate = StripUnconfigured { 925 | should_test: false, 926 | sess: &sess, 927 | features: None, 928 | } 929 | .fold_crate(krate); 930 | 931 | // Probe the crate to find all structs, unions and type aliases (used to convert type names 932 | // to names in C). 933 | let mut types = TyFinder { 934 | structs: HashSet::new(), 935 | unions: HashSet::new(), 936 | aliases: HashMap::new(), 937 | }; 938 | visit::walk_crate(&mut types, &krate); 939 | 940 | let mut gen = Generator { 941 | target: &target, 942 | rust: Box::new(rust_out), 943 | c: Box::new(c_out), 944 | sh: &sess.span_diagnostic, 945 | structs: types.structs, 946 | unions: types.unions, 947 | aliases: types.aliases, 948 | abi: Abi::C, 949 | tests: Vec::new(), 950 | files: HashSet::new(), 951 | sess: &sess, 952 | opts: self, 953 | }; 954 | t!(writeln!(gen.c, "#include ")); 955 | t!(writeln!(gen.c, "#include ")); 956 | t!(writeln!(gen.c, "#include ")); 957 | for header in &self.headers { 958 | t!(writeln!(gen.c, "#include <{}>", header)); 959 | } 960 | eprintln!("rust version: {}", self.rust_version); 961 | t!(gen.rust.write_all( 962 | if self.rust_version < rustc_version::Version::new(1, 30, 0) { 963 | br#" 964 | static FAILED: AtomicBool = std::sync::atomic::ATOMIC_BOOL_INIT; 965 | static NTESTS: AtomicUsize = std::sync::atomic::ATOMIC_USIZE_INIT; 966 | "# 967 | } else { 968 | br#" 969 | static FAILED: AtomicBool = AtomicBool::new(false); 970 | static NTESTS: AtomicUsize = AtomicUsize::new(0); 971 | "# 972 | } 973 | )); 974 | 975 | t!(gen.rust.write_all( 976 | br#" 977 | use std::mem; 978 | use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; 979 | 980 | fn main() { 981 | println!("RUNNING ALL TESTS"); 982 | run_all(); 983 | if FAILED.load(Ordering::SeqCst) { 984 | panic!("some tests failed"); 985 | } else { 986 | println!("PASSED {} tests", NTESTS.load(Ordering::SeqCst)); 987 | } 988 | } 989 | 990 | trait Pretty { 991 | fn pretty(&self) -> String; 992 | } 993 | 994 | impl<'a> Pretty for &'a str { 995 | fn pretty(&self) -> String { format!("{:?}", self) } 996 | } 997 | impl Pretty for *const T { 998 | fn pretty(&self) -> String { format!("{:?}", self) } 999 | } 1000 | impl Pretty for *mut T { 1001 | fn pretty(&self) -> String { format!("{:?}", self) } 1002 | } 1003 | macro_rules! p { 1004 | ($($i:ident)*) => ($( 1005 | impl Pretty for $i { 1006 | fn pretty(&self) -> String { 1007 | format!("{} ({:#x})", self, self) 1008 | } 1009 | } 1010 | )*) 1011 | } 1012 | p! { i8 i16 i32 i64 u8 u16 u32 u64 usize isize } 1013 | 1014 | fn same(rust: T, c: T, attr: &str) { 1015 | if rust != c { 1016 | println!("bad {}: rust: {} != c {}", attr, rust.pretty(), 1017 | c.pretty()); 1018 | FAILED.store(true, Ordering::SeqCst); 1019 | } else { 1020 | NTESTS.fetch_add(1, Ordering::SeqCst); 1021 | } 1022 | } 1023 | 1024 | macro_rules! offset_of { 1025 | ($ty:ident, $field:ident) => ( 1026 | (&((*(0 as *const $ty)).$field)) as *const _ as u64 1027 | ) 1028 | } 1029 | 1030 | "# 1031 | )); 1032 | 1033 | // Walk the crate, emitting test cases for everything found 1034 | visit::walk_crate(&mut gen, &krate); 1035 | gen.emit_run_all(); 1036 | 1037 | out_file 1038 | } 1039 | } 1040 | 1041 | #[allow(clippy::cognitive_complexity)] 1042 | fn default_cfg(target: &str) -> Vec<(String, Option)> { 1043 | let mut ret = Vec::new(); 1044 | let (arch, width, endian) = if target.starts_with("x86_64") { 1045 | if target.ends_with("x32") { 1046 | ("x86_64", "32", "little") 1047 | } else { 1048 | ("x86_64", "64", "little") 1049 | } 1050 | } else if target.starts_with("i386") || target.starts_with("i586") || target.starts_with("i686") 1051 | { 1052 | ("x86", "32", "little") 1053 | } else if target.starts_with("arm") { 1054 | ("arm", "32", "little") 1055 | } else if target.starts_with("aarch64") { 1056 | ("aarch64", "64", "little") 1057 | } else if target.starts_with("mipsel") { 1058 | ("mips", "32", "little") 1059 | } else if target.starts_with("mips64el") { 1060 | ("mips64", "64", "little") 1061 | } else if target.starts_with("mips64") { 1062 | ("mips64", "64", "big") 1063 | } else if target.starts_with("mips") { 1064 | ("mips", "32", "big") 1065 | } else if target.starts_with("powerpc64le") { 1066 | ("powerpc64", "64", "little") 1067 | } else if target.starts_with("powerpc64") { 1068 | ("powerpc64", "64", "big") 1069 | } else if target.starts_with("powerpc") { 1070 | ("powerpc", "32", "big") 1071 | } else if target.starts_with("s390x") { 1072 | ("s390x", "64", "big") 1073 | } else if target.starts_with("sparc64") { 1074 | ("sparc64", "64", "big") 1075 | } else if target.starts_with("asmjs") { 1076 | ("asmjs", "32", "little") 1077 | } else if target.starts_with("wasm32") { 1078 | ("wasm32", "32", "little") 1079 | } else { 1080 | panic!("unknown arch/pointer width: {}", target) 1081 | }; 1082 | let (os, family, env) = if target.contains("unknown-linux-gnu") { 1083 | ("linux", "unix", "gnu") 1084 | } else if target.contains("unknown-linux-musl") { 1085 | ("linux", "unix", "musl") 1086 | } else if target.contains("unknown-linux-uclibc") { 1087 | ("linux", "unix", "uclibc") 1088 | } else if target.contains("apple-darwin") { 1089 | ("macos", "unix", "") 1090 | } else if target.contains("apple-ios") { 1091 | ("ios", "unix", "") 1092 | } else if target.contains("windows-msvc") { 1093 | ("windows", "windows", "msvc") 1094 | } else if target.contains("windows-gnu") { 1095 | ("windows", "windows", "gnu") 1096 | } else if target.contains("android") { 1097 | ("android", "unix", "") 1098 | } else if target.contains("unknown-freebsd") { 1099 | ("freebsd", "unix", "") 1100 | } else if target.contains("netbsd") { 1101 | ("netbsd", "unix", "") 1102 | } else if target.contains("openbsd") { 1103 | ("openbsd", "unix", "") 1104 | } else if target.contains("dragonfly") { 1105 | ("dragonfly", "unix", "") 1106 | } else if target.contains("solaris") { 1107 | ("solaris", "unix", "") 1108 | } else if target.contains("emscripten") { 1109 | ("emscripten", "unix", "") 1110 | } else if target.contains("wasi") { 1111 | ("unknown", "", "wasi") 1112 | } else if target.contains("redox") { 1113 | ("redox", "unix", "") 1114 | } else if target.contains("vxworks") { 1115 | ("vxworks", "unix", "") 1116 | } else { 1117 | panic!("unknown os/family width: {}", target) 1118 | }; 1119 | 1120 | ret.push((family.to_string(), None)); 1121 | ret.push(("target_os".to_string(), Some(os.to_string()))); 1122 | ret.push(("target_family".to_string(), Some(family.to_string()))); 1123 | ret.push(("target_arch".to_string(), Some(arch.to_string()))); 1124 | ret.push(("target_pointer_width".to_string(), Some(width.to_string()))); 1125 | ret.push(("target_endian".to_string(), Some(endian.to_string()))); 1126 | ret.push(("target_env".to_string(), Some(env.to_string()))); 1127 | 1128 | ret 1129 | } 1130 | 1131 | fn linkage(lang: &Lang) -> &'static str { 1132 | match lang { 1133 | Lang::CXX => "extern \"C\"", 1134 | Lang::C => "", 1135 | } 1136 | } 1137 | 1138 | impl<'a> Generator<'a> { 1139 | fn rust2c_test(&self, ty: &str) -> bool { 1140 | let rustc_types = [ 1141 | "usize", "u8", "u16", "u32", "u64", "isize", "i8", "i16", "i32", "i64", 1142 | ]; 1143 | ty.starts_with("c_") || rustc_types.contains(&ty) 1144 | } 1145 | 1146 | fn rustmut2c(&self, mutbl: ast::Mutability) -> String { 1147 | match mutbl { 1148 | ast::Mutability::Immutable => "const ".to_string(), 1149 | ast::Mutability::Mutable => "".to_string(), 1150 | } 1151 | } 1152 | 1153 | fn rustmut2str(&self, mutbl: ast::Mutability) -> String { 1154 | match mutbl { 1155 | ast::Mutability::Immutable => "".to_string(), 1156 | ast::Mutability::Mutable => "mut ".to_string(), 1157 | } 1158 | } 1159 | 1160 | fn rust2c(&self, ty: &str) -> String { 1161 | match ty { 1162 | t if t.starts_with("c_") => match &ty[2..].replace("long", " long")[..] { 1163 | s if s.starts_with('u') => format!("unsigned {}", &s[1..]), 1164 | "short" => "short".to_string(), 1165 | s if s.starts_with('s') => format!("signed {}", &s[1..]), 1166 | s => s.to_string(), 1167 | }, 1168 | 1169 | "usize" => "size_t".to_string(), 1170 | "isize" => "ssize_t".to_string(), 1171 | "u8" => "uint8_t".to_string(), 1172 | "u16" => "uint16_t".to_string(), 1173 | "u32" => "uint32_t".to_string(), 1174 | "u64" => "uint64_t".to_string(), 1175 | "i8" => "int8_t".to_string(), 1176 | "i16" => "int16_t".to_string(), 1177 | "i32" => "int32_t".to_string(), 1178 | "i64" => "int64_t".to_string(), 1179 | "( )" => "void".to_string(), 1180 | s => (self.opts.type_name)(s, self.structs.contains(s), self.unions.contains(s)), 1181 | } 1182 | } 1183 | 1184 | fn rust2cfield(&self, struct_: &str, field: &str) -> String { 1185 | (self.opts.field_name)(struct_, field) 1186 | } 1187 | 1188 | fn test_type(&mut self, name: &str, ty: &ast::Ty) { 1189 | if (self.opts.skip_type)(name) { 1190 | if self.opts.verbose_skip { 1191 | eprintln!("skipping type \"{}\"", name); 1192 | } 1193 | return; 1194 | } 1195 | let c = self.rust_ty_to_c_ty(name); 1196 | self.test_size_align(name, &c); 1197 | self.test_sign(name, &c, ty); 1198 | } 1199 | 1200 | fn test_struct(&mut self, ty: &str, s: &ast::VariantData) { 1201 | if (self.opts.skip_struct)(ty) { 1202 | if self.opts.verbose_skip { 1203 | eprintln!("skipping struct \"{}\"", ty); 1204 | } 1205 | return; 1206 | } 1207 | 1208 | let cty = self.rust_ty_to_c_ty(ty); 1209 | self.test_size_align(ty, &cty); 1210 | 1211 | self.tests.push(format!("field_offset_size_{}", ty)); 1212 | t!(writeln!( 1213 | self.rust, 1214 | r#" 1215 | #[allow(non_snake_case)] 1216 | #[inline(never)] 1217 | fn field_offset_size_{ty}() {{ 1218 | "#, 1219 | ty = ty 1220 | )); 1221 | for field in s.fields() { 1222 | match field.vis { 1223 | ast::Visibility::Public => {} 1224 | _ => continue, 1225 | } 1226 | let name = match field.ident { 1227 | Some(name) => name, 1228 | None => panic!("no tuple structs in FFI"), 1229 | }; 1230 | let name = name.to_string(); 1231 | 1232 | if (self.opts.skip_field)(ty, &name) { 1233 | if self.opts.verbose_skip { 1234 | eprintln!("skipping field \"{}\" of struct \"{}\"", name, ty); 1235 | } 1236 | 1237 | continue; 1238 | } 1239 | 1240 | let cfield = self.rust2cfield(ty, &name); 1241 | 1242 | t!(writeln!( 1243 | self.c, 1244 | r#" 1245 | {linkage} uint64_t __test_offset_{ty}_{rust_field}(void) {{ 1246 | return offsetof({cstructty}, {c_field}); 1247 | }} 1248 | {linkage} uint64_t __test_fsize_{ty}_{rust_field}(void) {{ 1249 | {cstructty}* foo = NULL; 1250 | return sizeof(foo->{c_field}); 1251 | }} 1252 | "#, 1253 | ty = ty, 1254 | cstructty = cty, 1255 | rust_field = name, 1256 | c_field = cfield, 1257 | linkage = linkage(&self.opts.lang) 1258 | )); 1259 | 1260 | t!(writeln!( 1261 | self.rust, 1262 | r#" 1263 | extern {{ 1264 | #[allow(non_snake_case)] 1265 | fn __test_offset_{ty}_{field}() -> u64; 1266 | #[allow(non_snake_case)] 1267 | fn __test_fsize_{ty}_{field}() -> u64; 1268 | }} 1269 | unsafe {{ 1270 | let foo = 0 as *mut {ty}; 1271 | same(offset_of!({ty}, {field}), 1272 | __test_offset_{ty}_{field}(), 1273 | "field offset {field} of {ty}"); 1274 | same(mem::size_of_val(&(*foo).{field}) as u64, 1275 | __test_fsize_{ty}_{field}(), 1276 | "field size {field} of {ty}"); 1277 | }} 1278 | "#, 1279 | ty = ty, 1280 | field = name 1281 | )); 1282 | 1283 | if (self.opts.skip_field_type)(ty, &name.to_string()) { 1284 | if self.opts.verbose_skip { 1285 | eprintln!("skipping field type \"{}\" of struct \"{}\"", name, ty); 1286 | } 1287 | 1288 | continue; 1289 | } 1290 | 1291 | let sig = format!("__test_field_type_{}_{}({}* b)", ty, name, cty); 1292 | let mut sig = self.csig_returning_ptr(&field.ty, &sig); 1293 | if (self.opts.volatile_item)(VolatileItemKind::StructField( 1294 | ty.to_string(), 1295 | name.to_string(), 1296 | )) { 1297 | sig = format!("volatile {}", sig); 1298 | } 1299 | t!(writeln!( 1300 | self.c, 1301 | r#" 1302 | {linkage} {sig} {{ 1303 | return &b->{c_field}; 1304 | }} 1305 | "#, 1306 | sig = sig, 1307 | c_field = cfield, 1308 | linkage = linkage(&self.opts.lang) 1309 | )); 1310 | t!(writeln!( 1311 | self.rust, 1312 | r#" 1313 | extern {{ 1314 | #[allow(non_snake_case)] 1315 | fn __test_field_type_{ty}_{field}(a: *mut {ty}) 1316 | -> *mut u8; 1317 | }} 1318 | unsafe {{ 1319 | let foo = 0 as *mut {ty}; 1320 | same(&(*foo).{field} as *const _ as *mut _, 1321 | __test_field_type_{ty}_{field}(foo), 1322 | "field type {field} of {ty}"); 1323 | }} 1324 | "#, 1325 | ty = ty, 1326 | field = name 1327 | )); 1328 | } 1329 | t!(writeln!( 1330 | self.rust, 1331 | r#" 1332 | }} 1333 | "# 1334 | )); 1335 | } 1336 | 1337 | fn test_size_align(&mut self, rust: &str, c: &str) { 1338 | t!(writeln!( 1339 | self.c, 1340 | r#" 1341 | {linkage} uint64_t __test_size_{ty}(void) {{ return sizeof({cty}); }} 1342 | {linkage} uint64_t __test_align_{ty}(void) {{ 1343 | typedef struct {{ 1344 | unsigned char c; 1345 | {cty} v; 1346 | }} type; 1347 | type t; 1348 | size_t t_addr = (size_t)(unsigned char*)(&t); 1349 | size_t v_addr = (size_t)(unsigned char*)(&t.v); 1350 | return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; 1351 | }} 1352 | "#, 1353 | ty = rust, 1354 | cty = c, 1355 | linkage = linkage(&self.opts.lang) 1356 | )); 1357 | t!(writeln!( 1358 | self.rust, 1359 | r#" 1360 | #[allow(non_snake_case)] 1361 | #[inline(never)] 1362 | fn size_align_{ty}() {{ 1363 | extern {{ 1364 | #[allow(non_snake_case)] 1365 | fn __test_size_{ty}() -> u64; 1366 | #[allow(non_snake_case)] 1367 | fn __test_align_{ty}() -> u64; 1368 | }} 1369 | unsafe {{ 1370 | same(mem::size_of::<{ty}>() as u64, 1371 | __test_size_{ty}(), "{ty} size"); 1372 | same(mem::align_of::<{ty}>() as u64, 1373 | __test_align_{ty}(), "{ty} align"); 1374 | }} 1375 | }} 1376 | "#, 1377 | ty = rust 1378 | )); 1379 | self.tests.push(format!("size_align_{}", rust)); 1380 | } 1381 | 1382 | fn has_sign(&self, ty: &ast::Ty) -> bool { 1383 | match ty.node { 1384 | ast::TyKind::Path(_, ref path) => { 1385 | let last = path.segments.last().unwrap().identifier.to_string(); 1386 | if let Some(aliased) = self.aliases.get(&last) { 1387 | return self.has_sign(aliased); 1388 | } 1389 | match self.rust2c(&last).as_str() { 1390 | "char" | "short" | "int" | "long" | "long long" | "int8_t" | "int16_t" 1391 | | "int32_t" | "int64_t" | "uint8_t" | "uint16_t" | "uint32_t" | "uint64_t" 1392 | | "size_t" | "ssize_t" => true, 1393 | s => s.starts_with("signed ") || s.starts_with("unsigned "), 1394 | } 1395 | } 1396 | _ => false, 1397 | } 1398 | } 1399 | 1400 | fn test_sign(&mut self, rust: &str, c: &str, ty: &ast::Ty) { 1401 | if (self.opts.skip_signededness)(rust) { 1402 | if self.opts.verbose_skip { 1403 | eprintln!("skipping sign \"{}\"", rust); 1404 | } 1405 | 1406 | return; 1407 | } 1408 | if !self.has_sign(ty) { 1409 | return; 1410 | } 1411 | t!(writeln!( 1412 | self.c, 1413 | r#" 1414 | {linkage} uint32_t __test_signed_{ty}(void) {{ 1415 | return ((({cty}) -1) < 0); 1416 | }} 1417 | "#, 1418 | ty = rust, 1419 | cty = c, 1420 | linkage = linkage(&self.opts.lang) 1421 | )); 1422 | t!(writeln!( 1423 | self.rust, 1424 | r#" 1425 | #[inline(never)] 1426 | #[allow(non_snake_case)] 1427 | fn sign_{ty}() {{ 1428 | extern {{ 1429 | #[allow(non_snake_case)] 1430 | fn __test_signed_{ty}() -> u32; 1431 | }} 1432 | unsafe {{ 1433 | same(((!(0 as {ty})) < (0 as {ty})) as u32, 1434 | __test_signed_{ty}(), "{ty} signed"); 1435 | }} 1436 | }} 1437 | "#, 1438 | ty = rust 1439 | )); 1440 | self.tests.push(format!("sign_{}", rust)); 1441 | } 1442 | 1443 | fn rust_ty_to_c_ty(&self, mut rust_ty: &str) -> String { 1444 | if rust_ty == "&str" { 1445 | return "char*".to_string(); 1446 | } 1447 | let mut cty = self.rust2c(&rust_ty.replace("*mut ", "").replace("*const ", "")); 1448 | while rust_ty.starts_with('*') { 1449 | if rust_ty.starts_with("*const") { 1450 | cty = format!("const {}*", cty); 1451 | rust_ty = &rust_ty[7..]; 1452 | } else { 1453 | cty = format!("{}*", cty); 1454 | rust_ty = &rust_ty[5..]; 1455 | } 1456 | } 1457 | cty 1458 | } 1459 | 1460 | #[allow(clippy::similar_names)] 1461 | fn test_const(&mut self, name: &str, rust_ty: &str) { 1462 | if (self.opts.skip_const)(name) { 1463 | if self.opts.verbose_skip { 1464 | eprintln!("skipping const \"{}\"", name); 1465 | } 1466 | 1467 | return; 1468 | } 1469 | 1470 | let c_name = (self.opts.const_cname)(name); 1471 | 1472 | let cty = self.rust_ty_to_c_ty(rust_ty); 1473 | t!(writeln!( 1474 | self.c, 1475 | r#" 1476 | static const {cty} __test_const_{name}_val = {c_name}; 1477 | {linkage} const {cty}* __test_const_{name}(void) {{ 1478 | return &__test_const_{name}_val; 1479 | }} 1480 | "#, 1481 | name = name, 1482 | c_name = c_name, 1483 | cty = cty, 1484 | linkage = linkage(&self.opts.lang) 1485 | )); 1486 | 1487 | if rust_ty == "&str" { 1488 | t!(writeln!( 1489 | self.rust, 1490 | r#" 1491 | #[inline(never)] 1492 | #[allow(non_snake_case)] 1493 | fn const_{name}() {{ 1494 | extern {{ 1495 | #[allow(non_snake_case)] 1496 | fn __test_const_{name}() -> *const *const u8; 1497 | }} 1498 | let val = {name}; 1499 | unsafe {{ 1500 | let ptr = *__test_const_{name}(); 1501 | let c = ::std::ffi::CStr::from_ptr(ptr as *const _); 1502 | let c = c.to_str().expect("const {name} not utf8"); 1503 | same(val, c, "{name} string"); 1504 | }} 1505 | }} 1506 | "#, 1507 | name = name 1508 | )); 1509 | } else { 1510 | t!(writeln!( 1511 | self.rust, 1512 | r#" 1513 | #[allow(non_snake_case)] 1514 | fn const_{name}() {{ 1515 | extern {{ 1516 | #[allow(non_snake_case)] 1517 | fn __test_const_{name}() -> *const {ty}; 1518 | }} 1519 | let val = {name}; 1520 | unsafe {{ 1521 | let ptr1 = &val as *const _ as *const u8; 1522 | let ptr2 = __test_const_{name}() as *const u8; 1523 | for i in 0..mem::size_of::<{ty}>() {{ 1524 | let i = i as isize; 1525 | same(*ptr1.offset(i), *ptr2.offset(i), 1526 | &format!("{name} value at byte {{}}", i)); 1527 | }} 1528 | }} 1529 | }} 1530 | "#, 1531 | ty = rust_ty, 1532 | name = name 1533 | )); 1534 | } 1535 | self.tests.push(format!("const_{}", name)); 1536 | } 1537 | 1538 | fn test_extern_fn( 1539 | &mut self, 1540 | name: &str, 1541 | c_name: &Option, 1542 | args: &[String], 1543 | ret: &str, 1544 | variadic: bool, 1545 | abi: Abi, 1546 | ) { 1547 | if (self.opts.skip_fn)(name) { 1548 | if self.opts.verbose_skip { 1549 | eprintln!("skipping fn \"{}\"", name); 1550 | } 1551 | return; 1552 | } 1553 | let c_name = (self.opts.fn_cname)(name, c_name.as_ref().map(|s| &**s)); 1554 | let args = if args.is_empty() && !variadic { 1555 | "void".to_string() 1556 | } else { 1557 | args.iter() 1558 | .enumerate() 1559 | .map(|(idx, a)| { 1560 | let mut arg = self.rust_ty_to_c_ty(a); 1561 | if (self.opts.volatile_item)(VolatileItemKind::FunctionArg( 1562 | name.to_string(), 1563 | idx, 1564 | )) { 1565 | arg = format!("volatile {}", arg); 1566 | } 1567 | if (self.opts.array_arg)(name, idx) { 1568 | if let Some(last_ptr) = arg.rfind('*') { 1569 | arg = arg[..last_ptr].to_string(); 1570 | } else { 1571 | panic!("C FFI decl `{}` contains array argument", name); 1572 | } 1573 | } 1574 | arg 1575 | }) 1576 | .map(|s| { 1577 | if let Some(i) = s.rfind(']') { 1578 | let c = s.chars().filter(|&c| c == '*').count(); 1579 | if c == 0 { 1580 | return s; 1581 | } 1582 | let postfix_idx = s.find('[').unwrap(); 1583 | let postfix = &s[postfix_idx..=i]; 1584 | let prefix = &s[..postfix_idx]; 1585 | let pointers = &s[i + 1..]; 1586 | let has_const = pointers.contains("const"); 1587 | let pointers = pointers.replace("const *", "* const"); 1588 | let prefix = prefix.replacen("const", "", if has_const { 1 } else { 0 }); 1589 | return format!("{} ({}) {}", prefix, pointers, postfix); 1590 | } 1591 | s 1592 | }) 1593 | .collect::>() 1594 | .join(", ") 1595 | + if variadic { ", ..." } else { "" } 1596 | }; 1597 | let mut c_ret = self.rust_ty_to_c_ty(ret); 1598 | if (self.opts.volatile_item)(VolatileItemKind::FunctionRet(name.to_string())) { 1599 | c_ret = format!("volatile {}", c_ret); 1600 | } 1601 | let abi = self.abi2str(abi); 1602 | t!(writeln!( 1603 | self.c, 1604 | r#" 1605 | {ret} ({abi}*__test_fn_{name}(void))({args}) {{ 1606 | return {c_name}; 1607 | }} 1608 | "#, 1609 | name = name, 1610 | c_name = c_name, 1611 | args = args, 1612 | ret = c_ret, 1613 | abi = abi 1614 | )); 1615 | t!(writeln!( 1616 | self.rust, 1617 | r#" 1618 | #[allow(non_snake_case)] 1619 | #[inline(never)] 1620 | fn fn_{name}() {{ 1621 | extern {{ 1622 | #[allow(non_snake_case)] 1623 | fn __test_fn_{name}() -> *mut u32; 1624 | }} 1625 | unsafe {{ 1626 | if !{skip} {{ 1627 | same({name} as usize, 1628 | __test_fn_{name}() as usize, 1629 | "{name} function pointer"); 1630 | }} 1631 | }} 1632 | }} 1633 | "#, 1634 | name = name, 1635 | skip = (self.opts.skip_fn_ptrcheck)(name) 1636 | )); 1637 | if self.opts.verbose_skip && (self.opts.skip_fn_ptrcheck)(name) { 1638 | eprintln!("skipping fn ptr check \"{}\"", name); 1639 | } 1640 | 1641 | self.tests.push(format!("fn_{}", name)); 1642 | } 1643 | 1644 | fn test_extern_static( 1645 | &mut self, 1646 | name: &str, 1647 | c_name: Option, 1648 | rust_ty: &str, 1649 | c_ty: &str, 1650 | mutbl: bool, 1651 | ) { 1652 | if (self.opts.skip_static)(name) { 1653 | if self.opts.verbose_skip { 1654 | eprintln!("skipping static \"{}\"", name); 1655 | } 1656 | return; 1657 | } 1658 | 1659 | let c_name = c_name.unwrap_or_else(|| name.to_string()); 1660 | 1661 | if rust_ty.contains("extern fn") { 1662 | let sig = c_ty.replacen("(*)", &format!("(* __test_static_{}(void))", name), 1); 1663 | t!(writeln!( 1664 | self.c, 1665 | r#" 1666 | {sig} {{ 1667 | return {c_name}; 1668 | }} 1669 | "#, 1670 | sig = sig, 1671 | c_name = c_name 1672 | )); 1673 | t!(writeln!( 1674 | self.rust, 1675 | r#" 1676 | #[inline(never)] 1677 | #[allow(non_snake_case)] 1678 | fn static_{name}() {{ 1679 | extern {{ 1680 | #[allow(non_snake_case)] 1681 | fn __test_static_{name}() -> {ty}; 1682 | }} 1683 | unsafe {{ 1684 | same(*(&{name} as *const _ as *const {ty}) as usize, 1685 | __test_static_{name}() as usize, 1686 | "{name} static"); 1687 | }} 1688 | }} 1689 | "#, 1690 | name = name, 1691 | ty = rust_ty 1692 | )); 1693 | } else if rust_ty.starts_with('[') && rust_ty.ends_with(']') { 1694 | let c_ptr_ty = c_ty.split(' ').nth(0).unwrap(); 1695 | let mut lens = Vec::new(); 1696 | for i in c_ty.split(' ').skip(1) { 1697 | lens.push(i); 1698 | } 1699 | lens.reverse(); 1700 | let array_test_name = format!( 1701 | "{mutbl} {elem} (*__test_static_{name}(void)){lens}", 1702 | mutbl = if mutbl { "" } else { "const" }, 1703 | elem = c_ptr_ty, 1704 | name = name, 1705 | lens = lens.join("") 1706 | ); 1707 | t!(writeln!( 1708 | self.c, 1709 | r#" 1710 | {array_test_name} {{ 1711 | return &{c_name}; 1712 | }} 1713 | "#, 1714 | array_test_name = array_test_name, 1715 | c_name = c_name 1716 | )); 1717 | t!(writeln!( 1718 | self.rust, 1719 | r#" 1720 | #[inline(never)] 1721 | #[allow(non_snake_case)] 1722 | fn static_{name}() {{ 1723 | extern {{ 1724 | #[allow(non_snake_case)] 1725 | fn __test_static_{name}() -> *{mutbl} {ty}; 1726 | }} 1727 | unsafe {{ 1728 | same(&{name} as *const _ as usize, 1729 | __test_static_{name}() as usize, 1730 | "{name} static"); 1731 | }} 1732 | }} 1733 | "#, 1734 | name = name, 1735 | mutbl = if mutbl { "mut" } else { "const" }, 1736 | ty = rust_ty 1737 | )); 1738 | } else { 1739 | let c_ty = if (self.opts.volatile_item)(VolatileItemKind::Static(name.to_owned())) { 1740 | format!("volatile {}", c_ty) 1741 | } else { 1742 | c_ty.to_owned() 1743 | }; 1744 | 1745 | t!(writeln!( 1746 | self.c, 1747 | r#" 1748 | {mutbl}{ty}* __test_static_{name}(void) {{ 1749 | return &{c_name}; 1750 | }} 1751 | "#, 1752 | mutbl = if mutbl || c_ty.contains("const") { 1753 | "" 1754 | } else { 1755 | "const " 1756 | }, 1757 | ty = c_ty, 1758 | name = name, 1759 | c_name = c_name 1760 | )); 1761 | t!(writeln!( 1762 | self.rust, 1763 | r#" 1764 | #[allow(non_snake_case)] 1765 | #[inline(never)] 1766 | fn static_{name}() {{ 1767 | extern {{ 1768 | #[allow(non_snake_case)] 1769 | fn __test_static_{name}() -> *{mutbl} {ty}; 1770 | }} 1771 | unsafe {{ 1772 | same(&{name} as *const _ as usize, 1773 | __test_static_{name}() as usize, 1774 | "{name} static"); 1775 | }} 1776 | }} 1777 | "#, 1778 | name = name, 1779 | mutbl = if mutbl { "mut" } else { "const" }, 1780 | ty = rust_ty 1781 | )); 1782 | }; 1783 | self.tests.push(format!("static_{}", name)); 1784 | } 1785 | 1786 | fn test_roundtrip(&mut self, rust: &str, ast: Option<&ast::VariantData>) { 1787 | if (self.opts.skip_struct)(rust) { 1788 | if self.opts.verbose_skip { 1789 | eprintln!("skipping roundtrip (skip_struct) \"{}\"", rust); 1790 | } 1791 | return; 1792 | } 1793 | if (self.opts.skip_type)(rust) { 1794 | if self.opts.verbose_skip { 1795 | eprintln!("skipping roundtrip (skip_type) \"{}\"", rust); 1796 | } 1797 | return; 1798 | } 1799 | if (self.opts.skip_roundtrip)(rust) { 1800 | if self.opts.verbose_skip { 1801 | eprintln!("skipping roundtrip (skip_roundtrip)\"{}\"", rust); 1802 | } 1803 | return; 1804 | } 1805 | 1806 | let c = self.rust_ty_to_c_ty(rust); 1807 | 1808 | // Generate a function that returns a vector for a type 1809 | // that contains 1 if the byte is padding, and 0 if the byte is not 1810 | // padding: 1811 | t!(writeln!( 1812 | self.rust, 1813 | r#" 1814 | #[allow(non_snake_case, unused_mut, unused_variables, deprecated)] 1815 | #[inline(never)] 1816 | fn roundtrip_padding_{ty}() -> Vec {{ 1817 | // stores (offset, size) for each field 1818 | let mut v = Vec::<(usize, usize)>::new(); 1819 | let foo: {} = unsafe {{ std::mem::uninitialized() }}; 1820 | let foo = &foo as *const _ as *const {ty}; 1821 | "#, 1822 | ty = rust 1823 | )); 1824 | 1825 | if let Some(ast) = ast { 1826 | for field in ast.fields() { 1827 | // If a field is private, we can't access it, so 1828 | // we treat that as padding.. 1829 | match field.vis { 1830 | ast::Visibility::Public => {} 1831 | _ => continue, 1832 | } 1833 | 1834 | let name = match field.ident { 1835 | Some(name) => name, 1836 | None => panic!("no tuple structs in FFI"), 1837 | }; 1838 | let name = name.to_string(); 1839 | 1840 | t!(writeln!( 1841 | self.rust, 1842 | r#" 1843 | unsafe {{ 1844 | let size = mem::size_of_val(&(*foo).{field}); 1845 | let off = offset_of!({ty}, {field}) as usize; 1846 | v.push((off, size)); 1847 | }} 1848 | "#, 1849 | ty = rust, 1850 | field = name 1851 | )); 1852 | } 1853 | } 1854 | t!(writeln!( 1855 | self.rust, 1856 | r#" 1857 | // This vector contains `1` if the byte is padding 1858 | // and `0` if the byte is not padding. 1859 | let mut pad = Vec::::new(); 1860 | // Initialize all bytes as: 1861 | // - padding if we have fields, this means that only 1862 | // the fields will be checked 1863 | // - no-padding if we have a type alias: if this 1864 | // causes problems the type alias should be skipped 1865 | pad.resize(mem::size_of::<{ty}>(), {def}); 1866 | for (off, size) in &v {{ 1867 | for i in 0..*size {{ 1868 | pad[off + i] = 0; 1869 | }} 1870 | }} 1871 | pad 1872 | }} 1873 | "#, 1874 | ty = rust, 1875 | def = if ast.is_some() { 1 } else { 0 } 1876 | )); 1877 | 1878 | // Rust writes 1,2,3... to each byte of the type, passes 1879 | // the type to C by value exercising the call ABI. 1880 | // C verifies the bytes, writes the pattern 255,254,253... 1881 | // to it, and returns it by value. 1882 | // Rust reads it, and verifies it. The value `0` is never written 1883 | // to a byte (42 is used instead). Uninitialized memory is often 1884 | // all zeros, so for a single byte the test could return 1885 | // success even though it should have failed. 1886 | t!(writeln!( 1887 | self.c, 1888 | r#" 1889 | #ifdef _MSC_VER 1890 | // Disable signed/unsigned conversion warnings on MSVC. 1891 | // These trigger even if the conversion is explicit. 1892 | # pragma warning(disable:4365) 1893 | #endif 1894 | {linkage} {cty} __test_roundtrip_{ty}( 1895 | {cty} value, int32_t rust_size, int* error, unsigned char* pad 1896 | ) {{ 1897 | volatile unsigned char* p = (volatile unsigned char*)&value; 1898 | int size = (int)sizeof({cty}); 1899 | if (size != rust_size) {{ 1900 | fprintf( 1901 | stderr, 1902 | "size of {cty} is %d in C and %d in Rust\n", 1903 | (int)size, (int)rust_size 1904 | ); 1905 | *error = 1; 1906 | return value; 1907 | }} 1908 | int i = 0; 1909 | for (i = 0; i < size; ++i) {{ 1910 | if (pad[i]) {{ continue; }} 1911 | // fprintf(stdout, "C testing byte %d of %d of \"{ty}\"\n", i, size); 1912 | unsigned char c = (unsigned char)(i % 256); 1913 | c = c == 0? 42 : c; 1914 | if (p[i] != c) {{ 1915 | *error = 1; 1916 | fprintf( 1917 | stderr, 1918 | "rust[%d] = %d != %d (C): Rust \"{ty}\" -> C\n", 1919 | i, (int)p[i], (int)c 1920 | ); 1921 | }} 1922 | unsigned char d 1923 | = (unsigned char)(255) - (unsigned char)(i % 256); 1924 | d = d == 0? 42: d; 1925 | p[i] = d; 1926 | }} 1927 | return value; 1928 | }} 1929 | #ifdef _MSC_VER 1930 | # pragma warning(default:4365) 1931 | #endif 1932 | "#, 1933 | ty = rust, 1934 | cty = c, 1935 | linkage = linkage(&self.opts.lang), 1936 | )); 1937 | t!(writeln!( 1938 | self.rust, 1939 | r#" 1940 | #[allow(non_snake_case, deprecated)] 1941 | #[inline(never)] 1942 | fn roundtrip_{ty}() {{ 1943 | use libc::c_int; 1944 | type U = {ty}; 1945 | #[allow(improper_ctypes)] 1946 | extern {{ 1947 | #[allow(non_snake_case)] 1948 | fn __test_roundtrip_{ty}( 1949 | x: U, size: i32, e: *mut c_int, pad: *const u8 1950 | ) -> U; 1951 | }} 1952 | let pad = roundtrip_padding_{ty}(); 1953 | unsafe {{ 1954 | use std::mem::{{uninitialized, size_of}}; 1955 | let mut error: c_int = 0; 1956 | let mut y: U = uninitialized(); 1957 | let mut x: U = uninitialized(); 1958 | let x_ptr = &mut x as *mut _ as *mut u8; 1959 | let y_ptr = &mut y as *mut _ as *mut u8; 1960 | let sz = size_of::(); 1961 | for i in 0..sz {{ 1962 | let c: u8 = (i % 256) as u8; 1963 | let c = if c == 0 {{ 42 }} else {{ c }}; 1964 | let d: u8 = 255_u8 - (i % 256) as u8; 1965 | let d = if d == 0 {{ 42 }} else {{ d }}; 1966 | x_ptr.add(i).write_volatile(c); 1967 | y_ptr.add(i).write_volatile(d); 1968 | }} 1969 | let r: U = __test_roundtrip_{ty}(x, sz as i32, &mut error, pad.as_ptr()); 1970 | if error == 1 {{ 1971 | FAILED.store(true, Ordering::SeqCst); 1972 | return; 1973 | }} 1974 | for i in 0..size_of::() {{ 1975 | if pad[i] == 1 {{ continue; }} 1976 | // eprintln!("Rusta testing byte {{}} of {{}} of {ty}", i, size_of::()); 1977 | let rust = (*y_ptr.add(i)) as usize; 1978 | let c = (&r as *const _ as *const u8) 1979 | .add(i).read_volatile() as usize; 1980 | if rust != c {{ 1981 | eprintln!( 1982 | "rust [{{}}] = {{}} != {{}} (C): C \"{ty}\" -> Rust", 1983 | i, rust, c 1984 | ); 1985 | FAILED.store(true, Ordering::SeqCst); 1986 | }} 1987 | }} 1988 | }} 1989 | }} 1990 | "#, 1991 | ty = rust 1992 | )); 1993 | self.tests.push(format!("roundtrip_{}", rust)); 1994 | } 1995 | 1996 | fn assert_no_generics(&self, _i: ast::Ident, generics: &ast::Generics) { 1997 | assert!(generics.lifetimes.is_empty()); 1998 | assert!(generics.ty_params.is_empty()); 1999 | assert!(generics.where_clause.predicates.is_empty()); 2000 | } 2001 | 2002 | fn ty2name(&self, ty: &ast::Ty, rust: bool) -> String { 2003 | match ty.node { 2004 | ast::TyKind::Path(_, ref path) => { 2005 | let last = path.segments.last().unwrap(); 2006 | if last.identifier.to_string() == "Option" { 2007 | match last.parameters.as_ref().map(|p| &**p) { 2008 | Some(&ast::PathParameters::AngleBracketed(ref p)) => { 2009 | self.ty2name(&p.types[0], rust) 2010 | } 2011 | _ => panic!(), 2012 | } 2013 | } else if rust { 2014 | last.identifier.to_string() 2015 | } else { 2016 | self.rust2c(&last.identifier.to_string()) 2017 | } 2018 | } 2019 | ast::TyKind::Ptr(ref t) => { 2020 | if rust { 2021 | format!( 2022 | "*{} {}", 2023 | match t.mutbl { 2024 | ast::Mutability::Immutable => "const", 2025 | ast::Mutability::Mutable => "mut", 2026 | }, 2027 | self.ty2name(&t.ty, rust) 2028 | ) 2029 | } else { 2030 | let modifier = match t.mutbl { 2031 | ast::Mutability::Immutable => "const ", 2032 | ast::Mutability::Mutable => "", 2033 | }; 2034 | match t.ty.node { 2035 | ast::TyKind::BareFn(..) => self.ty2name(&t.ty, rust), 2036 | ast::TyKind::Ptr(..) => { 2037 | format!("{} {}*", self.ty2name(&t.ty, rust), modifier) 2038 | } 2039 | ast::TyKind::Array(ref t, ref e) => { 2040 | let len = self.expr2str(e); 2041 | let ty = self.ty2name(t, rust); 2042 | format!("{} {} [{}]", modifier, ty, len) 2043 | } 2044 | _ => format!("{}{}*", modifier, self.ty2name(&t.ty, rust)), 2045 | } 2046 | } 2047 | } 2048 | ast::TyKind::BareFn(ref t) => { 2049 | if rust { 2050 | let args = t 2051 | .decl 2052 | .inputs 2053 | .iter() 2054 | .map(|a| self.ty2name(&a.ty, rust)) 2055 | .collect::>() 2056 | .join(", "); 2057 | let ret = match t.decl.output { 2058 | ast::FunctionRetTy::Default(..) => "()".to_string(), 2059 | ast::FunctionRetTy::Ty(ref t) => self.ty2name(t, rust), 2060 | }; 2061 | format!("extern fn({}) -> {}", args, ret) 2062 | } else { 2063 | assert!(t.lifetimes.is_empty()); 2064 | let (ret, mut args, variadic) = self.decl2rust(&t.decl); 2065 | assert!(!variadic); 2066 | if args.is_empty() { 2067 | args.push("void".to_string()); 2068 | } 2069 | 2070 | if ret.contains("(*)") { 2071 | ret.replace("(*)", &format!("(*(*)({}))", args.join(", "))) 2072 | } else { 2073 | format!("{}(*)({})", ret, args.join(", ")) 2074 | } 2075 | } 2076 | } 2077 | ast::TyKind::Array(ref t, ref e) => { 2078 | if rust { 2079 | format!("[{}; {}]", self.ty2name(t, rust), self.expr2str(e)) 2080 | } else { 2081 | let len = self.expr2str(e); 2082 | let ty = self.ty2name(t, rust); 2083 | format!("{} [{}]", ty, len) 2084 | } 2085 | } 2086 | ast::TyKind::Rptr(l, ast::MutTy { ref ty, mutbl }) => { 2087 | let path = match ty.node { 2088 | ast::TyKind::Path(_, ref p) => p, 2089 | ast::TyKind::Array(ref t, _) => { 2090 | assert!(!rust); 2091 | return format!("{}{}*", self.rustmut2c(mutbl), self.ty2name(t, rust)); 2092 | } 2093 | _ => panic!("unknown ty {:?}", ty), 2094 | }; 2095 | if path.segments.len() != 1 { 2096 | panic!("unknown ty {:?}", ty) 2097 | } 2098 | match &*path.segments[0].identifier.name.as_str() { 2099 | "str" => { 2100 | if mutbl != ast::Mutability::Immutable { 2101 | panic!("unknown ty {:?}", ty) 2102 | } 2103 | if rust { 2104 | "&str".to_string() 2105 | } else { 2106 | "char*".to_string() 2107 | } 2108 | } 2109 | c if self.rust2c_test(c) => { 2110 | if rust { 2111 | match l { 2112 | Some(l) => format!( 2113 | "&{} {} {}", 2114 | l.ident.name.as_str(), 2115 | self.rustmut2str(mutbl), 2116 | self.ty2name(ty, rust) 2117 | ), 2118 | None => format!( 2119 | "&{:?} {}", 2120 | self.rustmut2str(mutbl), 2121 | self.ty2name(ty, rust) 2122 | ), 2123 | } 2124 | } else { 2125 | format!("{}{}*", self.rustmut2c(mutbl), self.rust2c(c)) 2126 | } 2127 | } 2128 | v => panic!("ref of unknown ty {:?} {:?} {:?} => {:?}", l, mutbl, ty, v), 2129 | } 2130 | } 2131 | ast::TyKind::Tup(ref v) if v.is_empty() => { 2132 | if rust { 2133 | "()".to_string() 2134 | } else { 2135 | "void".to_string() 2136 | } 2137 | } 2138 | _ => panic!("unknown ty {:?}", ty), 2139 | } 2140 | } 2141 | 2142 | fn csig_returning_ptr(&self, ty: &ast::Ty, sig: &str) -> String { 2143 | match ty.node { 2144 | ast::TyKind::Path(_, ref path) 2145 | if path.segments.last().unwrap().identifier.to_string() == "Option" => 2146 | { 2147 | let last = path.segments.last().unwrap(); 2148 | match last.parameters.as_ref().map(|s| &**s) { 2149 | Some(&ast::PathParameters::AngleBracketed(ref p)) => { 2150 | self.csig_returning_ptr(&p.types[0], sig) 2151 | } 2152 | _ => panic!(), 2153 | } 2154 | } 2155 | ast::TyKind::BareFn(ref t) => { 2156 | assert!(t.lifetimes.is_empty()); 2157 | let (ret, mut args, variadic) = self.decl2rust(&t.decl); 2158 | let abi = self.abi2str(t.abi); 2159 | if variadic { 2160 | args.push("...".to_string()); 2161 | } else if args.is_empty() { 2162 | args.push("void".to_string()); 2163 | } 2164 | format!("{}({}**{})({})", ret, abi, sig, args.join(", ")) 2165 | } 2166 | ast::TyKind::Array(ref t, ref e) => match t.node { 2167 | ast::TyKind::Array(ref t2, ref e2) => format!( 2168 | "{}(*{})[{}][{}]", 2169 | self.ty2name(t2, false), 2170 | sig, 2171 | self.expr2str(e), 2172 | self.expr2str(e2) 2173 | ), 2174 | _ => format!("{}(*{})[{}]", self.ty2name(t, false), sig, self.expr2str(e)), 2175 | }, 2176 | _ => format!("{}* {}", self.ty2name(ty, false), sig), 2177 | } 2178 | } 2179 | 2180 | fn expr2str(&self, e: &ast::Expr) -> String { 2181 | match e.node { 2182 | ast::ExprKind::Lit(ref l) => match l.node { 2183 | ast::LitKind::Int(a, _) => a.to_string(), 2184 | _ => panic!("unknown literal: {:?}", l), 2185 | }, 2186 | ast::ExprKind::Path(_, ref path) => { 2187 | path.segments.last().unwrap().identifier.to_string() 2188 | } 2189 | ast::ExprKind::Cast(ref e, _) => self.expr2str(e), 2190 | ast::ExprKind::Binary(ref op, ref e1, ref e2) => { 2191 | let e1 = self.expr2str(e1); 2192 | let e2 = self.expr2str(e2); 2193 | match op.node { 2194 | ast::BinOpKind::Add => format!("{} + {}", e1, e2), 2195 | _ => panic!("unknown op: {:?}", op), 2196 | } 2197 | } 2198 | _ => panic!("unknown expr: {:?}", e), 2199 | } 2200 | } 2201 | 2202 | fn abi2str(&self, abi: Abi) -> &'static str { 2203 | match abi { 2204 | Abi::C => "", 2205 | Abi::Stdcall => "__stdcall ", 2206 | Abi::System if self.target.contains("i686-pc-windows") => "__stdcall ", 2207 | Abi::System => "", 2208 | a => panic!("unknown ABI: {}", a), 2209 | } 2210 | } 2211 | 2212 | fn decl2rust(&self, decl: &ast::FnDecl) -> (String, Vec, bool) { 2213 | let args = decl 2214 | .inputs 2215 | .iter() 2216 | .map(|arg| self.ty2name(&arg.ty, false)) 2217 | .collect::>(); 2218 | let ret = match decl.output { 2219 | ast::FunctionRetTy::Default(..) => "void".to_string(), 2220 | ast::FunctionRetTy::Ty(ref t) => match t.node { 2221 | ast::TyKind::Never => "void".to_string(), 2222 | ast::TyKind::Tup(ref t) if t.is_empty() => "void".to_string(), 2223 | _ => self.ty2name(t, false), 2224 | }, 2225 | }; 2226 | (ret, args, decl.variadic) 2227 | } 2228 | 2229 | fn emit_run_all(&mut self) { 2230 | const N: usize = 1000; 2231 | let mut n = 0; 2232 | let mut tests = self.tests.clone(); 2233 | while tests.len() > N { 2234 | let name = format!("run_group{}", n); 2235 | n += 1; 2236 | t!(writeln!( 2237 | self.rust, 2238 | " 2239 | #[inline(never)] 2240 | fn {}() {{ 2241 | ", 2242 | name 2243 | )); 2244 | for test in tests.drain(..1000) { 2245 | t!(writeln!(self.rust, "{}();", test)); 2246 | } 2247 | t!(writeln!(self.rust, "}}")); 2248 | tests.push(name); 2249 | } 2250 | t!(writeln!( 2251 | self.rust, 2252 | " 2253 | #[inline(never)] 2254 | fn run_all() {{ 2255 | " 2256 | )); 2257 | for test in &tests { 2258 | t!(writeln!(self.rust, "{}();", test)); 2259 | } 2260 | t!(writeln!( 2261 | self.rust, 2262 | " 2263 | }} 2264 | " 2265 | )); 2266 | } 2267 | } 2268 | 2269 | impl<'a, 'v> Visitor<'v> for Generator<'a> { 2270 | fn visit_item(&mut self, i: &'v ast::Item) { 2271 | let prev_abi = self.abi; 2272 | let public = i.vis == ast::Visibility::Public; 2273 | match i.node { 2274 | ast::ItemKind::Ty(ref ty, ref generics) if public => { 2275 | self.assert_no_generics(i.ident, generics); 2276 | self.test_type(&i.ident.to_string(), ty); 2277 | self.test_roundtrip(&i.ident.to_string(), None); 2278 | } 2279 | 2280 | ast::ItemKind::Struct(ref s, ref generics) 2281 | | ast::ItemKind::Union(ref s, ref generics) 2282 | if public => 2283 | { 2284 | self.assert_no_generics(i.ident, generics); 2285 | let is_c = i.attrs.iter().any(|a| { 2286 | attr::find_repr_attrs(self.sh, a) 2287 | .iter() 2288 | .any(|a| *a == ReprAttr::ReprExtern || *a == ReprAttr::ReprTransparent) 2289 | }); 2290 | if !is_c && !(self.opts.skip_struct)(&i.ident.to_string()) { 2291 | panic!("{} is not marked #[repr(C)]", i.ident); 2292 | } 2293 | self.test_struct(&i.ident.to_string(), s); 2294 | self.test_roundtrip(&i.ident.to_string(), Some(s)); 2295 | } 2296 | 2297 | ast::ItemKind::Const(ref ty, _) if public => { 2298 | let ty = self.ty2name(ty, true); 2299 | self.test_const(&i.ident.to_string(), &ty); 2300 | } 2301 | 2302 | ast::ItemKind::ForeignMod(ref fm) => { 2303 | self.abi = fm.abi; 2304 | } 2305 | 2306 | _ => {} 2307 | } 2308 | let file = self.sess.codemap().span_to_filename(i.span); 2309 | if self.files.insert(file.clone()) { 2310 | println!("cargo:rerun-if-changed={}", file); 2311 | } 2312 | visit::walk_item(self, i); 2313 | self.abi = prev_abi; 2314 | } 2315 | 2316 | fn visit_foreign_item(&mut self, i: &'v ast::ForeignItem) { 2317 | match i.node { 2318 | ast::ForeignItemKind::Fn(ref decl, ref generics) => { 2319 | self.assert_no_generics(i.ident, generics); 2320 | for arg in &decl.inputs { 2321 | if let ast::TyKind::Array(_, _) = arg.ty.node { 2322 | panic!( 2323 | "Foreing Function decl `{}` uses array in C FFI", 2324 | &i.ident.to_string() 2325 | ); 2326 | } 2327 | } 2328 | 2329 | let (ret, args, variadic) = self.decl2rust(decl); 2330 | let c_name = attr::first_attr_value_str_by_name(&i.attrs, "link_name") 2331 | .map(|i| i.to_string()); 2332 | let abi = self.abi; 2333 | self.test_extern_fn(&i.ident.to_string(), &c_name, &args, &ret, variadic, abi); 2334 | } 2335 | ast::ForeignItemKind::Static(ref ty, mutbl) => { 2336 | let rust_ty = self.ty2name(&ty, true); 2337 | let c_ty = self.ty2name(&ty, false); 2338 | let c_name = attr::first_attr_value_str_by_name(&i.attrs, "link_name") 2339 | .map(|i| i.to_string()); 2340 | self.test_extern_static(&i.ident.to_string(), c_name, &rust_ty, &c_ty, mutbl); 2341 | } 2342 | } 2343 | visit::walk_foreign_item(self, i) 2344 | } 2345 | 2346 | fn visit_mac(&mut self, _mac: &'v ast::Mac) {} 2347 | } 2348 | 2349 | impl<'v> Visitor<'v> for TyFinder { 2350 | fn visit_item(&mut self, i: &'v ast::Item) { 2351 | match i.node { 2352 | ast::ItemKind::Struct(..) | ast::ItemKind::Enum(..) => { 2353 | self.structs.insert(i.ident.to_string()); 2354 | } 2355 | ast::ItemKind::Union(..) => { 2356 | self.unions.insert(i.ident.to_string()); 2357 | } 2358 | ast::ItemKind::Ty(ref ty, ..) => { 2359 | self.aliases.insert(i.ident.to_string(), ty.clone()); 2360 | } 2361 | 2362 | _ => {} 2363 | } 2364 | visit::walk_item(self, i) 2365 | } 2366 | fn visit_mac(&mut self, _mac: &'v ast::Mac) {} 2367 | } 2368 | 2369 | struct MyResolver<'a> { 2370 | parse_sess: &'a ParseSess, 2371 | id: usize, 2372 | map: HashMap>, 2373 | } 2374 | 2375 | impl<'a> Resolver for MyResolver<'a> { 2376 | fn next_node_id(&mut self) -> ast::NodeId { 2377 | self.id += 1; 2378 | ast::NodeId::new(self.id) 2379 | } 2380 | 2381 | fn get_module_scope(&mut self, _id: ast::NodeId) -> Mark { 2382 | Mark::root() 2383 | } 2384 | 2385 | fn eliminate_crate_var(&mut self, item: P) -> P { 2386 | item 2387 | } 2388 | 2389 | fn is_whitelisted_legacy_custom_derive(&self, _name: Name) -> bool { 2390 | false 2391 | } 2392 | 2393 | fn visit_expansion(&mut self, _invoc: Mark, expansion: &Expansion, _derives: &[Mark]) { 2394 | if let Expansion::Items(ref items) = expansion { 2395 | let features = RefCell::new(Features::new()); 2396 | for item in items.iter() { 2397 | MyVisitor { 2398 | parse_sess: self.parse_sess, 2399 | features: &features, 2400 | map: &mut self.map, 2401 | } 2402 | .visit_item(item); 2403 | } 2404 | } 2405 | } 2406 | 2407 | fn add_builtin(&mut self, _ident: ast::Ident, _ext: Rc) {} 2408 | 2409 | fn resolve_imports(&mut self) {} 2410 | 2411 | fn find_legacy_attr_invoc(&mut self, attrs: &mut Vec) -> Option { 2412 | attrs.retain(|a| !a.check_name("derive")); 2413 | None 2414 | } 2415 | 2416 | fn resolve_invoc( 2417 | &mut self, 2418 | invoc: &mut Invocation, 2419 | _scope: Mark, 2420 | _force: bool, 2421 | ) -> Result>, Determinacy> { 2422 | if let InvocationKind::Bang { ref mac, .. } = invoc.kind { 2423 | if mac.node.path.segments.len() != 1 { 2424 | return Ok(None); 2425 | } 2426 | let seg = &mac.node.path.segments[0]; 2427 | if seg.parameters.is_some() { 2428 | return Ok(None); 2429 | } 2430 | return Ok(self.map.get(&seg.identifier.name).cloned()); 2431 | } 2432 | Err(Determinacy::Determined) 2433 | } 2434 | 2435 | fn resolve_macro( 2436 | &mut self, 2437 | _scope: Mark, 2438 | _path: &ast::Path, 2439 | _kind: MacroKind, 2440 | _force: bool, 2441 | ) -> Result, Determinacy> { 2442 | Err(Determinacy::Determined) 2443 | } 2444 | 2445 | fn check_unused_macros(&self) {} 2446 | } 2447 | 2448 | struct StripUnchecked; 2449 | 2450 | impl Folder for StripUnchecked { 2451 | fn fold_item(&mut self, item: P) -> SmallVector> { 2452 | match item.node { 2453 | ast::ItemKind::Mod(..) 2454 | | ast::ItemKind::ForeignMod(..) 2455 | | ast::ItemKind::Ty(..) 2456 | | ast::ItemKind::Enum(..) 2457 | | ast::ItemKind::Struct(..) 2458 | | ast::ItemKind::Union(..) 2459 | | ast::ItemKind::Mac(..) 2460 | | ast::ItemKind::MacroDef(..) 2461 | | ast::ItemKind::Use(..) 2462 | | ast::ItemKind::ExternCrate(..) 2463 | | ast::ItemKind::Const(..) => fold::noop_fold_item(item, self), 2464 | 2465 | ast::ItemKind::Static(..) 2466 | | ast::ItemKind::Fn(..) 2467 | | ast::ItemKind::GlobalAsm(..) 2468 | | ast::ItemKind::Trait(..) 2469 | | ast::ItemKind::DefaultImpl(..) 2470 | | ast::ItemKind::Impl(..) => SmallVector::default(), 2471 | } 2472 | } 2473 | 2474 | fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac { 2475 | fold::noop_fold_mac(mac, self) 2476 | } 2477 | } 2478 | 2479 | struct MyVisitor<'b> { 2480 | parse_sess: &'b ParseSess, 2481 | features: &'b RefCell, 2482 | map: &'b mut HashMap>, 2483 | } 2484 | 2485 | impl<'a, 'b> Visitor<'a> for MyVisitor<'b> { 2486 | fn visit_item(&mut self, item: &'a ast::Item) { 2487 | if let ast::ItemKind::MacroDef(..) = item.node { 2488 | self.map.insert( 2489 | item.ident.name, 2490 | Rc::new(macro_rules::compile(self.parse_sess, self.features, item)), 2491 | ); 2492 | } 2493 | visit::walk_item(self, item); 2494 | } 2495 | 2496 | fn visit_mac(&mut self, _: &'a ast::Mac) { 2497 | /* ignore macros */ 2498 | } 2499 | } 2500 | 2501 | impl Default for TestGenerator { 2502 | fn default() -> Self { 2503 | Self::new() 2504 | } 2505 | } 2506 | -------------------------------------------------------------------------------- /testcrate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "testcrate" 3 | version = "0.1.0" 4 | authors = ["Alex Crichton "] 5 | build = "build.rs" 6 | 7 | [build-dependencies] 8 | ctest = { path = ".." } 9 | cc = "1.0" 10 | 11 | [dependencies] 12 | libc = "0.2" 13 | 14 | [lib] 15 | name = "testcrate" 16 | test = false 17 | doctest = false 18 | 19 | [[bin]] 20 | name = "t1" 21 | test = false 22 | 23 | [[bin]] 24 | name = "t2" 25 | test = false 26 | 27 | [[bin]] 28 | name = "t1_cxx" 29 | test = false 30 | 31 | [[bin]] 32 | name = "t2_cxx" 33 | test = false 34 | -------------------------------------------------------------------------------- /testcrate/build.rs: -------------------------------------------------------------------------------- 1 | extern crate cc; 2 | extern crate ctest; 3 | 4 | fn main() { 5 | use std::env; 6 | let opt_level = env::var("OPT_LEVEL") 7 | .ok() 8 | .and_then(|s| s.parse().ok()) 9 | .unwrap_or(0); 10 | let profile = env::var("PROFILE").unwrap_or(String::new()); 11 | if profile == "release" || opt_level >= 2 { 12 | println!("cargo:rustc-cfg=optimized"); 13 | } 14 | cc::Build::new() 15 | .include("src") 16 | .warnings(false) 17 | .file("src/t1.c") 18 | .compile("libt1.a"); 19 | println!("cargo:rerun-if-changed=src/t1.c"); 20 | println!("cargo:rerun-if-changed=src/t1.h"); 21 | cc::Build::new() 22 | .warnings(false) 23 | .file("src/t2.c") 24 | .compile("libt2.a"); 25 | println!("cargo:rerun-if-changed=src/t2.c"); 26 | println!("cargo:rerun-if-changed=src/t2.h"); 27 | ctest::TestGenerator::new() 28 | .header("t1.h") 29 | .include("src") 30 | .fn_cname(|a, b| b.unwrap_or(a).to_string()) 31 | .type_name(move |ty, is_struct, is_union| match ty { 32 | "T1Union" => ty.to_string(), 33 | "Transparent" => ty.to_string(), 34 | t if is_struct => format!("struct {}", t), 35 | t if is_union => format!("union {}", t), 36 | t => t.to_string(), 37 | }) 38 | .volatile_item(t1_volatile) 39 | .array_arg(t1_arrays) 40 | .skip_roundtrip(|n| n == "Arr") 41 | .generate("src/t1.rs", "t1gen.rs"); 42 | ctest::TestGenerator::new() 43 | .header("t2.h") 44 | .include("src") 45 | .type_name(move |ty, is_struct, is_union| match ty { 46 | "T2Union" => ty.to_string(), 47 | t if is_struct => format!("struct {}", t), 48 | t if is_union => format!("union {}", t), 49 | t => t.to_string(), 50 | }) 51 | .skip_roundtrip(|_| true) 52 | .generate("src/t2.rs", "t2gen.rs"); 53 | 54 | ctest::TestGenerator::new() 55 | .header("t1.h") 56 | .language(ctest::Lang::CXX) 57 | .include("src") 58 | .fn_cname(|a, b| b.unwrap_or(a).to_string()) 59 | .type_name(move |ty, is_struct, is_union| match ty { 60 | "T1Union" => ty.to_string(), 61 | "Transparent" => ty.to_string(), 62 | t if is_struct => format!("struct {}", t), 63 | t if is_union => format!("union {}", t), 64 | t => t.to_string(), 65 | }) 66 | .volatile_item(t1_volatile) 67 | .array_arg(t1_arrays) 68 | .skip_roundtrip(|n| n == "Arr") 69 | .generate("src/t1.rs", "t1gen_cxx.rs"); 70 | ctest::TestGenerator::new() 71 | .header("t2.h") 72 | .language(ctest::Lang::CXX) 73 | .include("src") 74 | .type_name(move |ty, is_struct, is_union| match ty { 75 | "T2Union" => ty.to_string(), 76 | t if is_struct => format!("struct {}", t), 77 | t if is_union => format!("union {}", t), 78 | t => t.to_string(), 79 | }) 80 | .skip_roundtrip(|_| true) 81 | .generate("src/t2.rs", "t2gen_cxx.rs"); 82 | } 83 | 84 | fn t1_volatile(i: ctest::VolatileItemKind) -> bool { 85 | use ctest::VolatileItemKind::*; 86 | match i { 87 | StructField(ref n, ref f) if n == "V" && f == "v" => true, 88 | Static(ref n) if n == "vol_ptr" => true, 89 | FunctionArg(ref n, 0) if n == "T1_vol0" => true, 90 | FunctionArg(ref n, 1) if n == "T1_vol2" => true, 91 | FunctionRet(ref n) if n == "T1_vol1" || n == "T1_vol2" => true, 92 | Static(ref n) if n == "T1_fn_ptr_vol" => true, 93 | _ => false, 94 | } 95 | } 96 | 97 | fn t1_arrays(n: &str, i: usize) -> bool { 98 | match n { 99 | "T1r" | "T1s" | "T1t" | "T1v" if i == 0 => true, 100 | _ => false, 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /testcrate/src/bin/t1.rs: -------------------------------------------------------------------------------- 1 | #![cfg(not(test))] 2 | #![deny(warnings)] 3 | 4 | extern crate libc; 5 | extern crate testcrate; 6 | use libc::*; 7 | use testcrate::t1::*; 8 | 9 | include!(concat!(env!("OUT_DIR"), "/t1gen.rs")); 10 | -------------------------------------------------------------------------------- /testcrate/src/bin/t1_cxx.rs: -------------------------------------------------------------------------------- 1 | #![cfg(not(test))] 2 | #![deny(warnings)] 3 | 4 | extern crate libc; 5 | extern crate testcrate; 6 | use libc::*; 7 | use testcrate::t1::*; 8 | 9 | include!(concat!(env!("OUT_DIR"), "/t1gen_cxx.rs")); 10 | -------------------------------------------------------------------------------- /testcrate/src/bin/t2.rs: -------------------------------------------------------------------------------- 1 | #![cfg(not(test))] 2 | #![deny(warnings)] 3 | 4 | extern crate testcrate; 5 | use testcrate::t2::*; 6 | 7 | include!(concat!(env!("OUT_DIR"), "/t2gen.rs")); 8 | -------------------------------------------------------------------------------- /testcrate/src/bin/t2_cxx.rs: -------------------------------------------------------------------------------- 1 | #![cfg(not(test))] 2 | #![deny(warnings)] 3 | 4 | extern crate testcrate; 5 | use testcrate::t2::*; 6 | 7 | include!(concat!(env!("OUT_DIR"), "/t2gen_cxx.rs")); 8 | -------------------------------------------------------------------------------- /testcrate/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | 3 | pub mod t1; 4 | pub mod t2; 5 | -------------------------------------------------------------------------------- /testcrate/src/t1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "t1.h" 4 | 5 | void T1a(void) {} 6 | void* T1b(void) { return NULL; } 7 | void* T1c(void* a) { return NULL; } 8 | int32_t T1d(unsigned a ) { return 0; } 9 | void T1e(unsigned a, const struct T1Bar* b) { } 10 | void T1f(void) {} 11 | void T1g(int32_t* a) {} 12 | void T1h(const int32_t* b) {} 13 | void T1i(int32_t a[4]) {} 14 | void T1j(const int32_t b[4]) {} 15 | void T1o(int32_t (*a)[4]) {} 16 | void T1p(int32_t (*const a)[4]) {} 17 | 18 | void T1r(Arr a) {} 19 | void T1s(const Arr a) {} 20 | void T1t(Arr* a) {} 21 | void T1v(const Arr* a) {} 22 | 23 | unsigned T1static = 3; 24 | 25 | const uint8_t T1_static_u8 = 42; 26 | uint8_t T1_static_mut_u8 = 37; 27 | 28 | uint8_t foo(uint8_t a, uint8_t b) { return a + b; } 29 | void bar(uint8_t a) { return; } 30 | void baz(void) { return; } 31 | 32 | uint32_t (*nested(uint8_t arg))(uint16_t) { 33 | return NULL; 34 | } 35 | 36 | uint32_t (*nested2(uint8_t(*arg0)(uint8_t), uint16_t(*arg1)(uint16_t)))(uint16_t) { 37 | return NULL; 38 | } 39 | 40 | uint8_t (*T1_static_mut_fn_ptr)(uint8_t, uint8_t) = foo; 41 | uint8_t (*const T1_static_const_fn_ptr_unsafe)(uint8_t, uint8_t) = foo; 42 | void (*const T1_static_const_fn_ptr_unsafe2)(uint8_t) = bar; 43 | void (*const T1_static_const_fn_ptr_unsafe3)(void) = baz; 44 | 45 | const uint8_t T1_static_right = 7; 46 | uint8_t (*T1_static_right2)(uint8_t, uint8_t) = foo; 47 | 48 | uint32_t (*(*T1_fn_ptr_s)(uint8_t))(uint16_t) = nested; 49 | uint32_t (*(*T1_fn_ptr_s2)(uint8_t(*arg0)(uint8_t), uint16_t(*arg1)(uint16_t)))(uint16_t) = nested2; 50 | 51 | const int32_t T1_arr0[2] = {0, 0}; 52 | const int32_t T1_arr1[2][3] = {{0, 0, 0}, {0, 0, 0}}; 53 | const int32_t T1_arr2[1][2][3] = {{{0, 0, 0}, {0, 0, 0}}}; 54 | 55 | int32_t T1_arr3[2] = {0, 0}; 56 | int32_t T1_arr4[2][3] = {{0, 0, 0}, {0, 0, 0}}; 57 | int32_t T1_arr5[1][2][3] = {{{0, 0, 0}, {0, 0, 0}}}; 58 | 59 | int32_t T1_arr42[1][2][3] = {{{0, 0, 0}, {0, 0, 0}}}; 60 | const int16_t* T1_sref = (void*)(1337); 61 | 62 | const int32_t* T1_mut_opt_ref = NULL; 63 | int32_t* T1_mut_opt_mut_ref = NULL; 64 | const int32_t* T1_const_opt_const_ref = NULL; 65 | 66 | void (*const T1_opt_fn1)(void) = baz; 67 | uint32_t (*(*T1_opt_fn2)(uint8_t))(uint16_t) = nested; 68 | uint32_t (*(*T1_opt_fn3)(uint8_t(*arg0)(uint8_t), uint16_t(*arg1)(uint16_t)))(uint16_t) = nested2; 69 | 70 | volatile uint8_t* vol_ptr = NULL; 71 | void* T1_vol0(volatile void* x, void* a) { return a? a: (void*)x; } 72 | volatile void* T1_vol1(void* x, void* b) { return b? (volatile void*)x : (volatile void*)x; } 73 | volatile void* T1_vol2(void* c, volatile void* x) { return c? x : x; } 74 | 75 | uint8_t (* volatile T1_fn_ptr_vol)(uint8_t, uint8_t) = foo; 76 | -------------------------------------------------------------------------------- /testcrate/src/t1.cpp: -------------------------------------------------------------------------------- 1 | t1.c -------------------------------------------------------------------------------- /testcrate/src/t1.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef int32_t T1Foo; 4 | 5 | #define T1N 5 6 | #define T1S "foo" 7 | 8 | struct T1Bar { 9 | int32_t a; 10 | uint32_t b; 11 | T1Foo c; 12 | uint8_t d; 13 | int64_t e[T1N]; 14 | int64_t f[T1N][2]; 15 | }; 16 | 17 | struct T1Baz { 18 | uint64_t a; 19 | struct T1Bar b; 20 | }; 21 | 22 | typedef union { 23 | uint64_t a; 24 | uint32_t b; 25 | } T1Union; 26 | 27 | union T1NoTypedefUnion { 28 | uint64_t a; 29 | uint32_t b; 30 | }; 31 | 32 | struct T1StructWithUnion { 33 | union T1NoTypedefUnion u; 34 | }; 35 | 36 | typedef double T1TypedefDouble; 37 | typedef int* T1TypedefPtr; 38 | typedef struct T1Bar T1TypedefStruct; 39 | 40 | void T1a(void); 41 | void* T1b(void); 42 | void* T1c(void*); 43 | int32_t T1d(unsigned); 44 | void T1e(unsigned, const struct T1Bar*); 45 | void T1f(void); 46 | void T1g(int32_t* a); 47 | void T1h(const int32_t* b); 48 | void T1i(int32_t a[4]); 49 | void T1j(const int32_t b[4]); 50 | void T1o(int32_t (*a)[4]); 51 | void T1p(int32_t (*const a)[4]); 52 | 53 | typedef int32_t (Arr)[4]; 54 | typedef int32_t Transparent; 55 | 56 | void T1r(Arr a); 57 | void T1s(const Arr a); 58 | void T1t(Arr* a); 59 | void T1v(const Arr* a); 60 | 61 | #define T1C 4 62 | 63 | extern uint32_t T1static; 64 | extern const uint8_t T1_static_u8; 65 | uint8_t T1_static_mut_u8; 66 | uint8_t (*T1_static_mut_fn_ptr)(uint8_t, uint8_t); 67 | extern uint8_t (*const T1_static_const_fn_ptr_unsafe)(uint8_t, uint8_t); 68 | extern void (*const T1_static_const_fn_ptr_unsafe2)(uint8_t); 69 | extern void (*const T1_static_const_fn_ptr_unsafe3)(void); 70 | 71 | extern const uint8_t T1_static_right; 72 | uint8_t (*T1_static_right2)(uint8_t, uint8_t); 73 | 74 | // T1_fn_ptr_nested: function pointer to a function, taking a uint8_t, and 75 | // returning a function pointer to a function taking a uint16_t and returning a 76 | // uint32_t 77 | uint32_t (*(*T1_fn_ptr_s)(uint8_t))(uint16_t); 78 | 79 | // T1_fn_ptr_nested: function pointer to a function, taking a function pointer 80 | // uint8_t -> uint8_t, and returning a function pointer to a function taking a 81 | // uint16_t and returning a uint32_t 82 | uint32_t (*(*T1_fn_ptr_s2)(uint8_t(*)(uint8_t), uint16_t(*)(uint16_t)))(uint16_t); 83 | 84 | extern const int32_t T1_arr0[2]; 85 | extern const int32_t T1_arr1[2][3]; 86 | extern const int32_t T1_arr2[1][2][3]; 87 | 88 | extern int32_t T1_arr3[2]; 89 | extern int32_t T1_arr4[2][3]; 90 | extern int32_t T1_arr5[1][2][3]; 91 | 92 | extern int32_t T1_arr42[1][2][3]; 93 | 94 | extern const int16_t* T1_sref; 95 | 96 | extern const int32_t* T1_mut_opt_ref; 97 | extern int32_t* T1_mut_opt_mut_ref; 98 | extern const int32_t* T1_const_opt_const_ref; 99 | 100 | extern void (*const T1_opt_fn1)(void); 101 | uint32_t (*(*T1_opt_fn2)(uint8_t))(uint16_t); 102 | uint32_t (*(*T1_opt_fn3)(uint8_t(*)(uint8_t), uint16_t(*)(uint16_t)))(uint16_t); 103 | 104 | 105 | struct Q { 106 | uint8_t* q0; 107 | uint8_t** q1; 108 | uint8_t q2; 109 | }; 110 | 111 | 112 | struct T1_conflict_foo { 113 | int a; 114 | }; 115 | 116 | struct T1_conflict{ 117 | int foo; 118 | }; 119 | 120 | // test packed structs 121 | // 122 | // on msvc there is only pragma pack 123 | // on clang and gcc there is a packed attribute 124 | 125 | # pragma pack(push,1) 126 | 127 | struct Pack { 128 | uint8_t a; 129 | uint16_t b; 130 | }; 131 | 132 | # pragma pack(pop) 133 | 134 | # pragma pack(push,4) 135 | 136 | struct Pack4 { 137 | uint8_t a; 138 | uint32_t b; 139 | }; 140 | 141 | # pragma pack(pop) 142 | 143 | // volatile pointers in struct fields: 144 | struct V { 145 | volatile uint8_t* v; 146 | }; 147 | 148 | // volatile pointers in externs: 149 | extern volatile uint8_t* vol_ptr; 150 | 151 | // volatile pointers in function arguments: 152 | void* T1_vol0(volatile void*, void*); 153 | volatile void* T1_vol1(void*, void*); 154 | volatile void* T1_vol2(void*, volatile void*); 155 | 156 | // volatile function pointers: 157 | uint8_t (*volatile T1_fn_ptr_vol)(uint8_t, uint8_t); 158 | 159 | #define LOG_MAX_LINE_LENGTH (1400) 160 | 161 | typedef struct { 162 | long tv_sec; 163 | int tv_usec; 164 | } timeval; 165 | 166 | typedef struct 167 | { 168 | long level; 169 | char const *file; 170 | long line; 171 | char const *module; 172 | timeval tv; 173 | char message[LOG_MAX_LINE_LENGTH]; 174 | } log_record_t; 175 | -------------------------------------------------------------------------------- /testcrate/src/t1.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use libc::*; 4 | 5 | pub type T1Foo = i32; 6 | pub const T1S: &'static str = "foo"; 7 | 8 | pub const T1N: i32 = 5; 9 | 10 | macro_rules! i { 11 | ($i:item) => { 12 | $i 13 | }; 14 | } 15 | 16 | #[repr(C)] 17 | pub struct T1Bar { 18 | pub a: i32, 19 | pub b: u32, 20 | pub c: T1Foo, 21 | pub d: u8, 22 | pub e: [i64; T1N as usize], 23 | pub f: [[i64; 2]; T1N as usize], 24 | } 25 | 26 | #[repr(C)] 27 | pub struct T1Baz { 28 | pub a: u64, 29 | pub b: T1Bar, 30 | } 31 | 32 | #[repr(C)] 33 | pub union T1Union { 34 | pub a: u64, 35 | pub b: u32, 36 | } 37 | 38 | #[repr(C)] 39 | pub union T1NoTypedefUnion { 40 | pub a: u64, 41 | pub b: u32, 42 | } 43 | 44 | #[repr(C)] 45 | pub struct T1StructWithUnion { 46 | pub u: T1NoTypedefUnion, 47 | } 48 | 49 | #[repr(transparent)] 50 | pub struct Transparent(i32); 51 | 52 | pub type T1TypedefDouble = c_double; 53 | pub type T1TypedefPtr = *mut c_int; 54 | pub type T1TypedefStruct = T1Bar; 55 | 56 | i! { 57 | pub const T1C: u32 = 4; 58 | } 59 | 60 | const NOT_PRESENT: u32 = 5; 61 | 62 | pub type Arr = [i32; 4]; 63 | 64 | extern "C" { 65 | pub fn T1a(); 66 | pub fn T1b() -> *mut c_void; 67 | pub fn T1c(a: *mut c_void) -> *mut c_void; 68 | pub fn T1d(a: c_uint) -> i32; 69 | pub fn T1e(a: c_uint, b: *const T1Bar); 70 | 71 | #[link_name = "T1f"] 72 | pub fn f() -> (); 73 | 74 | pub fn T1g(a: *mut [i32; 4]); 75 | pub fn T1h(a: *const [i32; 4]) -> !; 76 | pub fn T1i(a: *mut [i32; 4]); 77 | pub fn T1j(a: *const [i32; 4]) -> !; 78 | pub fn T1o(a: *mut *mut [i32; 4]); 79 | pub fn T1p(a: *const *const [i32; 4]) -> !; 80 | 81 | pub fn T1r(a: *mut Arr); 82 | pub fn T1s(a: *const Arr) -> !; 83 | pub fn T1t(a: *mut *mut Arr); 84 | pub fn T1v(a: *const *const Arr) -> !; 85 | 86 | pub static T1static: c_uint; 87 | } 88 | 89 | pub fn foo() { 90 | assert_eq!(1, 1); 91 | } 92 | 93 | extern "C" { 94 | pub static T1_static_u8: u8; 95 | pub static mut T1_static_mut_u8: u8; 96 | pub static mut T1_static_mut_fn_ptr: extern "C" fn(u8, u8) -> u8; 97 | pub static T1_static_const_fn_ptr_unsafe: unsafe extern "C" fn(u8, u8) -> u8; 98 | pub static T1_static_const_fn_ptr_unsafe2: unsafe extern "C" fn(u8) -> (); 99 | pub static T1_static_const_fn_ptr_unsafe3: unsafe extern "C" fn() -> (); 100 | 101 | #[link_name = "T1_static_right"] 102 | pub static T1_static_wrong: u8; 103 | #[link_name = "T1_static_right2"] 104 | pub static mut T1_static_wrong2: extern "C" fn(u8, u8) -> u8; 105 | 106 | pub static T1_fn_ptr_s: unsafe extern "C" fn(u8) -> extern "C" fn(u16) -> u32; 107 | pub static T1_fn_ptr_s2: unsafe extern "C" fn( 108 | extern "C" fn(u8) -> u8, 109 | extern "C" fn(u16) -> u16, 110 | ) -> extern "C" fn(u16) -> u32; 111 | 112 | pub static T1_arr0: [i32; 2]; 113 | pub static T1_arr1: [[i32; 3]; 2]; 114 | pub static T1_arr2: [[[i32; 3]; 2]; 1]; 115 | 116 | pub static mut T1_arr3: [i32; 2]; 117 | pub static mut T1_arr4: [[i32; 3]; 2]; 118 | pub static mut T1_arr5: [[[i32; 3]; 2]; 1]; 119 | 120 | #[link_name = "T1_arr42"] 121 | pub static mut T1_arr6: [[[i32; 3]; 2]; 1]; 122 | 123 | pub static mut T1_sref: &'static i16; 124 | 125 | pub static mut T1_mut_opt_ref: Option<&'static i32>; 126 | pub static mut T1_mut_opt_mut_ref: Option<&'static mut i32>; 127 | pub static T1_const_opt_const_ref: Option<&'static i32>; 128 | 129 | pub static T1_opt_fn1: Option ()>; 130 | pub static T1_opt_fn2: Option extern "C" fn(u16) -> u32>; 131 | pub static T1_opt_fn3: Option< 132 | unsafe extern "C" fn( 133 | extern "C" fn(u8) -> u8, 134 | extern "C" fn(u16) -> u16, 135 | ) -> extern "C" fn(u16) -> u32, 136 | >; 137 | } 138 | 139 | #[repr(C)] 140 | pub struct Q { 141 | pub q0: *mut u8, 142 | pub q1: *mut *mut u8, 143 | pub q2: u8, 144 | } 145 | 146 | #[repr(C)] 147 | pub struct T1_conflict_foo { 148 | a: i32, 149 | } 150 | 151 | #[repr(C)] 152 | pub struct T1_conflict { 153 | pub foo: i32, 154 | } 155 | 156 | #[repr(C, packed)] 157 | pub struct Pack { 158 | pub a: u8, 159 | pub b: u16, 160 | } 161 | 162 | #[repr(C, packed(4))] 163 | pub struct Pack4 { 164 | pub a: u8, 165 | pub b: u32, 166 | } 167 | 168 | #[repr(C)] 169 | pub struct V { 170 | pub v: *mut u8, 171 | } 172 | 173 | extern "C" { 174 | pub static mut vol_ptr: *mut u8; 175 | pub fn T1_vol0(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; 176 | pub fn T1_vol1(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; 177 | pub fn T1_vol2(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; 178 | pub static T1_fn_ptr_vol: Option u8>; 179 | } 180 | 181 | pub const LOG_MAX_LINE_LENGTH: usize = 1400; 182 | 183 | #[repr(C)] 184 | struct timeval { 185 | tv_sec: c_long, 186 | tv_usec: c_int, 187 | } 188 | 189 | #[repr(C)] 190 | struct log_record_t { 191 | level: c_long, 192 | file: *const c_char, 193 | line: c_long, 194 | module: *const c_char, 195 | tv: timeval, 196 | message: [c_char; LOG_MAX_LINE_LENGTH], 197 | } 198 | -------------------------------------------------------------------------------- /testcrate/src/t2.c: -------------------------------------------------------------------------------- 1 | 2 | void T2a() {} 3 | -------------------------------------------------------------------------------- /testcrate/src/t2.cpp: -------------------------------------------------------------------------------- 1 | t2.c -------------------------------------------------------------------------------- /testcrate/src/t2.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef int32_t T2Foo; 4 | typedef int8_t T2Bar; 5 | 6 | typedef T2Foo T2TypedefFoo; 7 | typedef unsigned T2TypedefInt; 8 | 9 | struct T2Baz { 10 | int8_t _a; 11 | int64_t a; 12 | uint32_t b; 13 | }; 14 | 15 | typedef struct { 16 | uint32_t a; 17 | int64_t b; 18 | } T2Union; 19 | 20 | static void T2a(void) {} 21 | 22 | #define T2C 4 23 | #define T2S "a" 24 | -------------------------------------------------------------------------------- /testcrate/src/t2.rs: -------------------------------------------------------------------------------- 1 | use libc::*; 2 | 3 | pub type T2Foo = u32; 4 | pub type T2Bar = u32; 5 | 6 | pub type T2TypedefFoo = T2Foo; 7 | pub type T2TypedefInt = c_int; 8 | 9 | macro_rules! i { 10 | ($i:item) => { 11 | $i 12 | }; 13 | } 14 | 15 | #[repr(C)] 16 | #[derive(Debug)] 17 | pub struct T2Baz { 18 | pub a: i64, 19 | pub b: u32, 20 | } 21 | 22 | #[repr(C)] 23 | pub union T2Union { 24 | pub a: u32, 25 | pub b: i64, 26 | } 27 | 28 | pub const T2C: i32 = 5; 29 | 30 | i! { 31 | pub const T2S: &'static str = "b"; 32 | } 33 | 34 | extern "C" { 35 | pub fn T2a(); 36 | } 37 | -------------------------------------------------------------------------------- /testcrate/tests/all.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::env; 3 | use std::process::{Command, ExitStatus}; 4 | 5 | fn cmd(name: &str) -> Command { 6 | let mut p = env::current_exe().unwrap(); 7 | p.pop(); 8 | if p.file_name().unwrap().to_str() == Some("deps") { 9 | p.pop(); 10 | } 11 | p.push(name); 12 | Command::new(p) 13 | } 14 | 15 | #[test] 16 | fn t1() { 17 | let (o, status) = output(&mut cmd("t1")); 18 | assert!(status.success(), o); 19 | assert!(!o.contains("bad "), o); 20 | eprintln!("o: {}", o); 21 | } 22 | 23 | #[test] 24 | fn t1_cxx() { 25 | let (o, status) = output(&mut cmd("t1_cxx")); 26 | assert!(status.success(), o); 27 | assert!(!o.contains("bad "), o); 28 | } 29 | 30 | #[test] 31 | fn t2() { 32 | let (o, status) = output(&mut cmd("t2")); 33 | assert!(!status.success(), o); 34 | let errors = [ 35 | "bad T2Foo signed", 36 | "bad T2TypedefFoo signed", 37 | "bad T2TypedefInt signed", 38 | "bad T2Bar size", 39 | "bad T2Bar align", 40 | "bad T2Bar signed", 41 | "bad T2Baz size", 42 | "bad field offset a of T2Baz", 43 | "bad field type a of T2Baz", 44 | "bad field offset b of T2Baz", 45 | "bad field type b of T2Baz", 46 | "bad T2a function pointer", 47 | "bad T2C value at byte 0", 48 | "bad T2S string", 49 | "bad T2Union size", 50 | "bad field type b of T2Union", 51 | "bad field offset b of T2Union", 52 | ]; 53 | let mut errors = errors.iter().cloned().collect::>(); 54 | 55 | let mut bad = false; 56 | for line in o.lines().filter(|l| l.starts_with("bad ")) { 57 | let msg = &line[..line.find(":").unwrap()]; 58 | if !errors.remove(&msg) { 59 | println!("unknown error: {}", msg); 60 | bad = true; 61 | } 62 | } 63 | 64 | for error in errors { 65 | println!("didn't find error: {}", error); 66 | bad = true; 67 | } 68 | if bad { 69 | println!("output was:\n\n{}", o); 70 | panic!(); 71 | } 72 | } 73 | 74 | #[test] 75 | fn t2_cxx() { 76 | let (o, status) = output(&mut cmd("t2_cxx")); 77 | assert!(!status.success(), o); 78 | let errors = [ 79 | "bad T2Foo signed", 80 | "bad T2TypedefFoo signed", 81 | "bad T2TypedefInt signed", 82 | "bad T2Bar size", 83 | "bad T2Bar align", 84 | "bad T2Bar signed", 85 | "bad T2Baz size", 86 | "bad field offset a of T2Baz", 87 | "bad field type a of T2Baz", 88 | "bad field offset b of T2Baz", 89 | "bad field type b of T2Baz", 90 | "bad T2a function pointer", 91 | "bad T2C value at byte 0", 92 | "bad T2S string", 93 | "bad T2Union size", 94 | "bad field type b of T2Union", 95 | "bad field offset b of T2Union", 96 | ]; 97 | let mut errors = errors.iter().cloned().collect::>(); 98 | 99 | let mut bad = false; 100 | for line in o.lines().filter(|l| l.starts_with("bad ")) { 101 | let msg = &line[..line.find(":").unwrap()]; 102 | if !errors.remove(&msg) { 103 | println!("unknown error: {}", msg); 104 | bad = true; 105 | } 106 | } 107 | 108 | for error in errors { 109 | println!("didn't find error: {}", error); 110 | bad = true; 111 | } 112 | if bad { 113 | println!("output was:\n\n{}", o); 114 | panic!(); 115 | } 116 | } 117 | 118 | fn output(cmd: &mut Command) -> (String, ExitStatus) { 119 | let output = cmd.output().unwrap(); 120 | let stdout = String::from_utf8(output.stdout).unwrap(); 121 | let stderr = String::from_utf8(output.stderr).unwrap(); 122 | 123 | (stdout + &stderr, output.status) 124 | } 125 | --------------------------------------------------------------------------------