Skip to content

Commit

Permalink
Auto merge of #254 - lennart/CLOUD-13-postcompile, r=TroyKomodo
Browse files Browse the repository at this point in the history
postcompile: docs and tests
- Adds docs
- Adds tests and improves coverage
  - I'm not sure how to cover the remaining lines in postcompile since most of them are related to failing rustc and cargo commands. There is not a reliable way to crash rustc or cargo as far as I know.

CLOUD-13

Requested-by: TroyKomodo <[email protected]>
Reviewed-by: TroyKomodo <[email protected]>
  • Loading branch information
scuffle-brawl[bot] authored Jan 20, 2025
2 parents 4442703 + 27d93a9 commit a3c1c77
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 2 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions crates/postcompile/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ license = "MIT OR Apache-2.0"
description = "Helper crate for post-compiling Rust code."
keywords = ["postcompile", "snapshot", "test", "proc-macro"]

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(trybuild_no_target)', 'cfg(postcompile_no_target)', 'cfg(coverage_nightly)'] }

[dependencies]
serde_json = "1.0"
cargo_metadata = "0.19.1"
Expand All @@ -22,8 +25,8 @@ prettyplease = { version = "0.2", optional = true }
syn = { version = "2", features = ["full"] }
scuffle-workspace-hack.workspace = true

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(trybuild_no_target)', 'cfg(postcompile_no_target)'] }
[dev-dependencies]
insta = "1.42.0"

[features]
prettyplease = ["dep:prettyplease"]
155 changes: 155 additions & 0 deletions crates/postcompile/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,91 @@
//! A crate which allows you to compile Rust code at runtime (hence the name
//! `postcompile`).
//!
//! What that means is that you can provide the input to `rustc` and then get
//! back the expanded output, compiler errors, warnings, etc.
//!
//! This is particularly useful when making snapshot tests of proc-macros, look
//! below for an example with the `insta` crate.
//!
//! ## Usage
//!
//! ```rs
//! #[test]
//! fn some_cool_test() {
//! insta::assert_snapshot!(postcompile::compile! {
//! #![allow(unused)]
//!
//! #[derive(Debug, Clone)]
//! struct Test {
//! a: u32,
//! b: i32,
//! }
//!
//! const TEST: Test = Test { a: 1, b: 3 };
//! });
//! }
//!
//! #[test]
//! fn some_cool_test_extern() {
//! insta::assert_snapshot!(postcompile::compile_str!(include_str!("some_file.rs")));
//! }
//! ```
//!
//! ## Features
//!
//! - Cached builds: This crate reuses the cargo build cache of the original
//! crate so that only the contents of the macro are compiled & not any
//! additional dependencies.
//! - Coverage: This crate works with [`cargo-llvm-cov`](https://crates.io/crates/cargo-llvm-cov)
//! out of the box, which allows you to instrument the proc-macro expansion.
//!
//! ## Alternatives
//!
//! - [`compiletest_rs`](https://crates.io/crates/compiletest_rs): This crate is
//! used by the Rust compiler team to test the compiler itself. Not really
//! useful for proc-macros.
//! - [`trybuild`](https://crates.io/crates/trybuild): This crate is an
//! all-in-one solution for testing proc-macros, with built in snapshot
//! testing.
//! - [`ui_test`](https://crates.io/crates/ui_test): Similar to `trybuild` with
//! a slightly different API & used by the Rust compiler team to test the
//! compiler itself.
//!
//! ### Differences
//!
//! The other libraries are focused on testing & have built in test harnesses.
//! This crate takes a step back and allows you to compile without a testing
//! harness. This has the advantage of being more flexible, and allows you to
//! use whatever testing framework you want.
//!
//! In the examples above I showcase how to use this crate with the `insta`
//! crate for snapshot testing.
//!
//! ## Status
//!
//! This crate is currently under development and is not yet stable.
//!
//! Unit tests are not yet fully implemented. Use at your own risk.
//!
//! ## Limitations
//!
//! Please note that this crate does not work inside a running compiler process
//! (inside a proc-macro) without hacky workarounds and complete build-cache
//! invalidation.
//!
//! This is because `cargo` holds a lock on the build directory and that if we
//! were to compile inside a proc-macro we would recursively invoke the
//! compiler.
//!
//! ## License
//!
//! This project is licensed under the [MIT](./LICENSE.MIT) or
//! [Apache-2.0](./LICENSE.Apache-2.0) license. You can choose between one of
//! them if you use this work.
//!
//! `SPDX-License-Identifier: MIT OR Apache-2.0`
#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))]

use std::borrow::Cow;
use std::ffi::{OsStr, OsString};
use std::os::unix::ffi::OsStrExt;
Expand Down Expand Up @@ -294,3 +382,70 @@ macro_rules! try_compile_str {
$crate::compile_custom($expr, &$crate::_config!())
};
}

#[cfg(test)]
#[cfg_attr(all(test, coverage_nightly), coverage(off))]
mod tests {
use insta::assert_snapshot;

use crate::ExitStatus;

#[test]
fn compile_success() {
let out = compile! {
#[allow(unused)]
fn main() {
let a = 1;
let b = 2;
let c = a + b;
}
};

assert_eq!(out.status, ExitStatus::Success);
assert!(out.stderr.is_empty());
assert_snapshot!(out);
}

#[test]
fn try_compile_success() {
let out = try_compile! {
#[allow(unused)]
fn main() {
let xd = 0xd;
let xdd = 0xdd;
let xddd = xd + xdd;
println!("{}", xddd);
}
};

assert!(out.is_ok());
let out = out.unwrap();
assert_eq!(out.status, ExitStatus::Success);
assert!(out.stderr.is_empty());
assert!(!out.stdout.is_empty());
}

#[test]
fn compile_failure() {
let out = compile! {
invalid_rust_code
};

assert_eq!(out.status, ExitStatus::Failure(1));
assert!(out.stdout.is_empty());
assert_snapshot!(out);
}

#[test]
fn try_compile_failure() {
let out = try_compile! {
invalid rust code
};

assert!(out.is_ok());
let out = out.unwrap();
assert_eq!(out.status, ExitStatus::Failure(1));
assert!(out.stdout.is_empty());
assert!(!out.stderr.is_empty());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
source: crates/postcompile/src/lib.rs
expression: out
snapshot_kind: text
---
exit status: 1
--- stderr
error: expected one of `!` or `::`, found `<eof>`
--> <postcompile>:1:1
|
1 | invalid_rust_code
| ^^^^^^^^^^^^^^^^^ expected one of `!` or `::`
error: aborting due to 1 previous error
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
source: crates/postcompile/src/lib.rs
expression: out
snapshot_kind: text
---
exit status: 0
--- stdout
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
#[allow(unused)]
fn main() {
let a = 1;
let b = 2;
let c = a + b;
}

0 comments on commit a3c1c77

Please sign in to comment.