Skip to content

Commit 5381e37

Browse files
authored
[testutil] Initial commit (#412)
1 parent cc7a0eb commit 5381e37

File tree

6 files changed

+192
-44
lines changed

6 files changed

+192
-44
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ itertools = "0.11"
6060
rand = { version = "0.8.5", features = ["small_rng"] }
6161
rustversion = "1.0"
6262
static_assertions = "1.1"
63+
testutil = { path = "testutil" }
6364
# Pinned to a specific version so that the version used for local development
6465
# and the version used in CI are guaranteed to be the same. Future versions
6566
# sometimes change the output format slightly, so a version mismatch can cause

tests/trybuild.rs

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,18 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
// UI tests depend on the exact error messages emitted by rustc, but those error
6-
// messages are not stable, and sometimes change between Rust versions. Thus, we
7-
// maintain one set of UI tests for each Rust version that we test in CI, and we
8-
// pin to specific versions in CI (a specific stable version, a specific date of
9-
// the nightly compiler, and a specific MSRV). Updating those pinned versions
10-
// may also require updating these tests.
11-
// - `tests/ui-nightly` - Contains the source of truth for our UI test source
12-
// files (`.rs`), and contains `.err` and `.out` files for nightly
13-
// - `tests/ui-stable` - Contains symlinks to the `.rs` files in
14-
// `tests/ui-nightly`, and contains `.err` and `.out` files for stable
15-
// - `tests/ui-msrv` - Contains symlinks to the `.rs` files in
16-
// `tests/ui-nightly`, and contains `.err` and `.out` files for MSRV
17-
18-
#[rustversion::nightly]
19-
const SOURCE_FILES_DIR: &str = "tests/ui-nightly";
20-
#[rustversion::stable(1.69.0)]
21-
const SOURCE_FILES_DIR: &str = "tests/ui-stable";
22-
#[rustversion::stable(1.61.0)]
23-
const SOURCE_FILES_DIR: &str = "tests/ui-msrv";
5+
use testutil::ToolchainVersion;
246

257
#[test]
268
#[cfg_attr(miri, ignore)]
279
fn ui() {
10+
let version = ToolchainVersion::extract_from_pwd().unwrap();
11+
// See the doc comment on this method for an explanation of what this does
12+
// and why we store source files in different directories.
13+
let source_files_dirname = version.get_ui_source_files_dirname_and_maybe_print_warning();
14+
2815
let t = trybuild::TestCases::new();
29-
t.compile_fail(format!("{SOURCE_FILES_DIR}/*.rs"));
16+
t.compile_fail(format!("tests/{source_files_dirname}/*.rs"));
3017
}
3118

3219
// The file `invalid-impls.rs` directly includes `src/macros.rs` in order to
@@ -40,6 +27,11 @@ fn ui() {
4027
#[test]
4128
#[cfg_attr(miri, ignore)]
4229
fn ui_invalid_impls() {
30+
let version = ToolchainVersion::extract_from_pwd().unwrap();
31+
// See the doc comment on this method for an explanation of what this does
32+
// and why we store source files in different directories.
33+
let source_files_dirname = version.get_ui_source_files_dirname_and_maybe_print_warning();
34+
4335
let t = trybuild::TestCases::new();
44-
t.compile_fail(format!("{SOURCE_FILES_DIR}/invalid-impls/*.rs"));
36+
t.compile_fail(format!("tests/{source_files_dirname}/invalid-impls/*.rs"));
4537
}

testutil/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright 2023 The Fuchsia Authors. All rights reserved.
2+
# Use of this source code is governed by a BSD-style license that can be
3+
# found in the LICENSE file.
4+
5+
[package]
6+
name = "testutil"
7+
version = "0.0.0"
8+
edition = "2021"
9+
10+
[dependencies]
11+
cargo_metadata = "0.18.0"
12+
rustc_version = "0.4.0"
13+
# Pin to 0.3.0 because more recent versions require a Rust version more recent
14+
# than our MSRV.
15+
time = { version = "=0.3.0", default-features = false, features = ["formatting", "macros", "parsing"] }

testutil/src/lib.rs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright 2023 The Fuchsia Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
use cargo_metadata::MetadataCommand;
6+
use rustc_version::{Channel, Version};
7+
use std::error::Error;
8+
9+
struct PinnedVersions {
10+
msrv: String,
11+
stable: String,
12+
nightly: String,
13+
}
14+
15+
impl PinnedVersions {
16+
/// Attempts to extract pinned toolchain versions based on the current
17+
/// working directory.
18+
///
19+
/// `extract_from_pwd` expects to be called from a directory which is a
20+
/// child of a Cargo workspace. It extracts the pinned versions from the
21+
/// metadata of the root package.
22+
fn extract_from_pwd() -> Result<PinnedVersions, Box<dyn Error>> {
23+
let meta = MetadataCommand::new().exec()?;
24+
// NOTE(joshlf): In theory `meta.root_package()` should work instead of
25+
// this manual search, but for some reason it breaks when called from
26+
// zerocopy-derive's tests. This works as a workaround, and it's just
27+
// test code, so I didn't bother investigating.
28+
let pkg = meta
29+
.workspace_packages()
30+
.into_iter()
31+
.find(|pkg| pkg.name == "zerocopy")
32+
.ok_or("no `zerocopy` package found; are we in a workspace?")?;
33+
let msrv = pkg
34+
.rust_version
35+
.as_ref()
36+
.ok_or("failed to find msrv: no `rust-version` key present")?
37+
.to_string();
38+
let extract = |version_name, key| -> Result<String, String> {
39+
let value = pkg.metadata.pointer(&format!("/ci/{key}")).ok_or_else(|| {
40+
format!("failed to find {version_name}: no `metadata.ci.{key}` key present")
41+
})?;
42+
value.as_str().map(str::to_string).ok_or_else(|| format!("failed to find {version_name}: key `metadata.ci.{key}` (contents: {value:?}) failed to parse as JSON string"))
43+
};
44+
let stable = extract("stable", "pinned-stable")?;
45+
let nightly = extract("nightly", "pinned-nightly")?;
46+
Ok(PinnedVersions { msrv, stable, nightly })
47+
}
48+
}
49+
50+
#[derive(Debug)]
51+
pub enum ToolchainVersion {
52+
/// The version listed as our MSRV (ie, the `package.rust-version` key in
53+
/// `Cargo.toml`).
54+
PinnedMsrv,
55+
/// The stable version pinned in CI.
56+
PinnedStable,
57+
/// The nightly version pinned in CI
58+
PinnedNightly,
59+
/// A stable version other than the one pinned in CI.
60+
OtherStable,
61+
/// A nightly version other than the one pinned in CI.
62+
OtherNightly,
63+
}
64+
65+
impl ToolchainVersion {
66+
/// Attempts to determine whether the current toolchain version matches one
67+
/// of the versions pinned in CI and if so, which one.
68+
pub fn extract_from_pwd() -> Result<ToolchainVersion, Box<dyn Error>> {
69+
let pinned_versions = PinnedVersions::extract_from_pwd()?;
70+
let current = rustc_version::version_meta()?;
71+
72+
let s = match current.channel {
73+
Channel::Dev | Channel::Beta => {
74+
return Err(format!("unsupported channel: {:?}", current.channel).into())
75+
}
76+
Channel::Nightly => {
77+
format!(
78+
"nightly-{}",
79+
current.commit_date.as_ref().ok_or("nightly channel missing commit date")?
80+
)
81+
}
82+
Channel::Stable => {
83+
let Version { major, minor, patch, .. } = current.semver;
84+
format!("{major}.{minor}.{patch}")
85+
}
86+
};
87+
88+
// Due to a quirk of how Rust nightly versions are encoded and published
89+
// [1], the version as understood by rustup uses a date one day ahead of
90+
// the version as encoded in the `rustc` binary itself.
91+
// `pinned_versions` encodes the former notion of the date (as it is
92+
// meant to be passed as the `+<toolchain>` selector syntax understood
93+
// by rustup), while `current` encodes the latter notion of the date (as
94+
// it is extracted from `rustc`). Without this adjustment, toolchain
95+
// versions that should be considered equal would not be.
96+
//
97+
// [1] https://github.com/rust-lang/rust/issues/51533
98+
let pinned_nightly_adjusted = {
99+
let desc = time::macros::format_description!("nightly-[year]-[month]-[day]");
100+
let date = time::Date::parse(&pinned_versions.nightly, &desc).map_err(|_| {
101+
format!("failed to parse nightly version: {}", pinned_versions.nightly)
102+
})?;
103+
let adjusted = date - time::Duration::DAY;
104+
adjusted.format(&desc).unwrap()
105+
};
106+
107+
Ok(match s {
108+
s if s == pinned_versions.msrv => ToolchainVersion::PinnedMsrv,
109+
s if s == pinned_versions.stable => ToolchainVersion::PinnedStable,
110+
s if s == pinned_nightly_adjusted => ToolchainVersion::PinnedNightly,
111+
_ if current.channel == Channel::Stable => ToolchainVersion::OtherStable,
112+
_ if current.channel == Channel::Nightly => ToolchainVersion::OtherNightly,
113+
_ => {
114+
return Err(format!(
115+
"current toolchain ({current:?}) doesn't match any known version"
116+
)
117+
.into())
118+
}
119+
})
120+
}
121+
122+
/// Gets the name of the directory in which to store source files and
123+
/// expected output for UI tests for this toolchain version.
124+
///
125+
/// For toolchain versions which are not pinned in CI, prints a warning to
126+
/// `stderr` which will be captured by the test harness and only printed on
127+
/// test failure.
128+
///
129+
/// UI tests depend on the exact error messages emitted by rustc, but those
130+
/// error messages are not stable, and sometimes change between Rust
131+
/// versions. Thus, we maintain one set of UI tests for each Rust version
132+
/// that we test in CI, and we pin to specific versions in CI (a specific
133+
/// MSRV, a specific stable version, and a specific date of the nightly
134+
/// compiler). Updating those pinned versions may also require updating
135+
/// these tests.
136+
/// - `tests/ui-nightly` - Contains the source of truth for our UI test
137+
/// source files (`.rs`), and contains `.err` and `.out` files for nightly
138+
/// - `tests/ui-stable` - Contains symlinks to the `.rs` files in
139+
/// `tests/ui-nightly`, and contains `.err` and `.out` files for stable
140+
/// - `tests/ui-msrv` - Contains symlinks to the `.rs` files in
141+
/// `tests/ui-nightly`, and contains `.err` and `.out` files for MSRV
142+
pub fn get_ui_source_files_dirname_and_maybe_print_warning(&self) -> &'static str {
143+
if matches!(self, ToolchainVersion::OtherStable | ToolchainVersion::OtherNightly) {
144+
// This will be eaten by the test harness and only displayed on
145+
// failure.
146+
eprintln!("warning: current toolchain does not match any toolchain pinned in CI; this may cause spurious test failure");
147+
}
148+
149+
match self {
150+
ToolchainVersion::PinnedMsrv => "ui-msrv",
151+
ToolchainVersion::PinnedStable | ToolchainVersion::OtherStable => "ui-stable",
152+
ToolchainVersion::PinnedNightly | ToolchainVersion::OtherNightly => "ui-nightly",
153+
}
154+
}
155+
}

zerocopy-derive/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ quote = "1.0.10"
2323
syn = "2.0.31"
2424

2525
[dev-dependencies]
26-
rustversion = "1.0"
2726
static_assertions = "1.1"
27+
testutil = { path = "../testutil" }
2828
# Pinned to a specific version so that the version used for local development
2929
# and the version used in CI are guaranteed to be the same. Future versions
3030
# sometimes change the output format slightly, so a version mismatch can cause
3131
# CI test failures.
3232
trybuild = "=1.0.80"
33-
zerocopy = { path = "../", features = ["default", "derive"] }
33+
zerocopy = { path = "../", features = ["default", "derive"] }

zerocopy-derive/tests/trybuild.rs

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,14 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
// UI tests depend on the exact error messages emitted by rustc, but those error
6-
// messages are not stable, and sometimes change between Rust versions. Thus, we
7-
// maintain one set of UI tests for each Rust version that we test in CI, and we
8-
// pin to specific versions in CI (a specific stable version, a specific date of
9-
// the nightly compiler, and a specific MSRV). Updating those pinned versions
10-
// may also require updating these tests.
11-
// - `tests/ui-nightly` - Contains the source of truth for our UI test source
12-
// files (`.rs`), and contains `.err` and `.out` files for nightly
13-
// - `tests/ui-stable` - Contains symlinks to the `.rs` files in
14-
// `tests/ui-nightly`, and contains `.err` and `.out` files for stable
15-
// - `tests/ui-msrv` - Contains symlinks to the `.rs` files in
16-
// `tests/ui-nightly`, and contains `.err` and `.out` files for MSRV
17-
18-
#[rustversion::nightly]
19-
const SOURCE_FILES_GLOB: &str = "tests/ui-nightly/*.rs";
20-
#[rustversion::stable(1.69.0)]
21-
const SOURCE_FILES_GLOB: &str = "tests/ui-stable/*.rs";
22-
#[rustversion::stable(1.61.0)]
23-
const SOURCE_FILES_GLOB: &str = "tests/ui-msrv/*.rs";
24-
255
#[test]
266
#[cfg_attr(miri, ignore)]
277
fn ui() {
8+
let version = testutil::ToolchainVersion::extract_from_pwd().unwrap();
9+
// See the doc comment on this method for an explanation of what this does
10+
// and why we store source files in different directories.
11+
let source_files_dirname = version.get_ui_source_files_dirname_and_maybe_print_warning();
12+
2813
let t = trybuild::TestCases::new();
29-
t.compile_fail(SOURCE_FILES_GLOB);
14+
t.compile_fail(format!("tests/{source_files_dirname}/*.rs"));
3015
}

0 commit comments

Comments
 (0)