Skip to content

Commit 1de7a2a

Browse files
committed
Use prebuilt binaries
1 parent 5011728 commit 1de7a2a

File tree

6 files changed

+167
-79
lines changed

6 files changed

+167
-79
lines changed

.github/workflows/ci.yml

Lines changed: 12 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ jobs:
5858
5959
- uses: actions/checkout@v5
6060

61-
- uses: actions/cache/restore@v4
62-
id: cache-restore
61+
- uses: actions/cache@v4
6362
with:
6463
key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
6564
path: |
@@ -70,31 +69,15 @@ jobs:
7069
~/.cargo/git/db/
7170
~/.dylint_drivers/
7271
~/.rustup/toolchains/
73-
agave/
7472
target/dylint/
7573
7674
- name: Rustup
7775
run: rustup update
7876

79-
- name: Install Agave prerequisites
77+
- name: Install Anchor prerequisites
8078
run: |
8179
sudo apt update
82-
sudo apt install libclang-dev libudev-dev llvm protobuf-compiler
83-
84-
- name: Install Agave
85-
run: |
86-
if ! ./agave/bin/solana-test-validator --version; then
87-
git clone https://github.com/anza-xyz/agave
88-
cd agave
89-
# smoelius: The next commit merges `SBPF v0.12.2`: https://github.com/anza-xyz/agave/pull/7498
90-
git checkout cd291424d3d71c1a3be0c2c919916dcaa272d162
91-
sed -i '/^\[patch\.crates-io\]$/a solana-sbpf = { git = "https://github.com/trail-of-forks/sbpf-coverage" }' Cargo.toml
92-
# smoelius: `solana` is not used directly, but it is called by `anchor`.
93-
sed -i '/^binArgs=()$/i BINS=(cargo-build-sbf solana-test-validator solana); DCOU_BINS=()' scripts/cargo-install-all.sh
94-
./scripts/cargo-install-all.sh .
95-
cd ..
96-
fi
97-
echo "$PWD/agave/bin" >> "$GITHUB_PATH"
80+
sudo apt install libudev-dev
9881
9982
# smoelius: https://www.anchor-lang.com/docs/installation
10083
- name: Install Anchor
@@ -120,32 +103,22 @@ jobs:
120103

121104
- name: Sanity test fixtures
122105
run: |
106+
AGAVE_TAG="$(cat agave_tag.txt)"
123107
for X in fixtures/*; do
124-
pushd "$X" && yarn && anchor test && popd
108+
pushd "$X"
109+
yarn
110+
wget https://github.com/trail-of-forks/sbpf-coverage/releases/download/$AGAVE_TAG/patched-agave-tools-$AGAVE_TAG-Linux.tar.gz
111+
tar xzf patched-agave-tools-$AGAVE_TAG-Linux.tar.gz
112+
rm -f patched-agave-tools-$AGAVE_TAG-Linux.tar.gz
113+
PATH="$PWD/patched-agave-tools-$AGAVE_TAG-Linux/bin:$PATH"
114+
anchor test
115+
popd
125116
done
126117
127118
- name: Test
128119
run: |
129120
cargo test --config "$GROUP_RUNNER"
130121
131-
# https://github.com/actions/cache/tree/main/save#always-save-cache
132-
- uses: actions/cache/save@v4
133-
# smoelius: Update the cache regardless of whether a cache hit occurred, in case further
134-
# progress was made.
135-
if: always() # && steps.cache-restore.outputs.cache-hit != 'true'
136-
with:
137-
key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
138-
path: |
139-
~/.avm
140-
~/.cargo/bin/
141-
~/.cargo/registry/index/
142-
~/.cargo/registry/cache/
143-
~/.cargo/git/db/
144-
~/.dylint_drivers/
145-
~/.rustup/toolchains/
146-
agave/
147-
target/dylint/
148-
149122
all-checks:
150123
needs: [test]
151124

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
/target
22
/coverage
33
/fixtures/*/sbf_trace_dir
4+
patched-agave-tools-*

agave_tag.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v3.0.6

src/bin/anchor-coverage.rs

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
use anchor_coverage::util::StripCurrentDir;
1+
use anchor_coverage::util::{var_guard::VarGuard, StripCurrentDir};
22
use anyhow::{bail, ensure, Result};
33
use std::{
4-
env::{args, current_dir},
5-
fs::{canonicalize, create_dir_all, remove_dir_all},
4+
env::{args, current_dir, join_paths, split_paths, var_os},
5+
ffi::OsString,
6+
fmt::Write,
7+
fs::{canonicalize, create_dir_all, read, remove_dir_all},
68
path::{Path, PathBuf},
79
process::Command,
810
};
@@ -34,6 +36,18 @@ Usage: {0} [ANCHOR_TEST_ARGS]...
3436

3537
let current_dir = current_dir()?;
3638

39+
// smoelius: Set `PATH` now, once and for all. This way subsequent calls to `which` will return
40+
// paths to the tools actually used.
41+
let _guard: VarGuard;
42+
if let Some(path_buf) = anchor_coverage::util::patched_agave_tools(&current_dir)? {
43+
eprintln!(
44+
"Found patched Agave tools: {}",
45+
path_buf.strip_current_dir().display()
46+
);
47+
let prepended_paths = prepend_paths(path_buf.join("bin"))?;
48+
_guard = VarGuard::set("PATH", Some(prepended_paths));
49+
}
50+
3751
let sbf_trace_dir = current_dir.join("sbf_trace_dir");
3852

3953
if sbf_trace_dir.try_exists()? {
@@ -48,21 +62,30 @@ Usage: {0} [ANCHOR_TEST_ARGS]...
4862
let pcs_paths = anchor_coverage::util::files_with_extension(&sbf_trace_dir, "pcs")?;
4963

5064
if pcs_paths.is_empty() {
51-
let try_running_message = grep_command()
52-
.map(|command| {
53-
format!(
54-
" Try running the following command:
55-
{command}"
56-
)
57-
})
58-
.unwrap_or_default();
59-
60-
bail!(
61-
"Found no program counter files in: {}
62-
Are you sure your `solana-test-validator` is patched?{}",
63-
sbf_trace_dir.strip_current_dir().display(),
64-
try_running_message
65+
let mut message = format!(
66+
"Found no program counter files in: {}",
67+
sbf_trace_dir.strip_current_dir().display()
6568
);
69+
let path = which("solana-test-validator")?;
70+
if !solana_test_validator_is_patched(&path)? {
71+
#[rustfmt::skip]
72+
write!(
73+
&mut message,
74+
"\n
75+
`{}` does not appear to be patched.
76+
77+
Either download, unzip, and untar prebuilt patched binaries from:
78+
79+
https://github.com/trail-of-forks/sbpf-coverage/releases
80+
81+
Or build patched binaries from source using the instructions at:
82+
83+
https://github.com/trail-of-forks/sbpf-coverage",
84+
path.display()
85+
)
86+
.unwrap();
87+
}
88+
bail!(message);
6689
}
6790

6891
anchor_coverage::run(sbf_trace_dir, options.debug)?;
@@ -90,6 +113,16 @@ fn parse_args() -> Options {
90113
Options { args, debug, help }
91114
}
92115

116+
fn prepend_paths(path: PathBuf) -> Result<OsString> {
117+
let Some(paths) = var_os("PATH") else {
118+
bail!("`PATH` is unset");
119+
};
120+
let paths_split = split_paths(&paths);
121+
let paths_chained = std::iter::once(path).chain(paths_split);
122+
let paths_joined = join_paths(paths_chained)?;
123+
Ok(paths_joined)
124+
}
125+
93126
fn anchor_test_with_debug(args: &[String], sbf_trace_dir: &Path) -> Result<()> {
94127
#[cfg(feature = "__anchor_cli")]
95128
anchor_coverage::__build_with_debug(
@@ -126,12 +159,12 @@ fn anchor_test_skip_build(args: &[String], sbf_trace_dir: &Path) -> Result<()> {
126159
Ok(())
127160
}
128161

129-
fn grep_command() -> Result<String> {
130-
let path = which("solana-test-validator")?;
131-
Ok(format!(
132-
"grep SBF_TRACE_DIR {} || echo 'solana-test-validator is not patched'",
133-
path.display()
134-
))
162+
fn solana_test_validator_is_patched(path: &Path) -> Result<bool> {
163+
let contents = read(path)?;
164+
let needle = "SBF_TRACE_DIR";
165+
Ok(contents
166+
.windows(needle.len())
167+
.any(|w| w == needle.as_bytes()))
135168
}
136169

137170
fn which(filename: &str) -> Result<PathBuf> {

src/tests.rs

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
1-
use crate::util::files_with_extension;
2-
use anyhow::{ensure, Result};
1+
use crate::util::{files_with_extension, patched_agave_tools};
2+
use anyhow::{anyhow, ensure, Result};
33
use assert_cmd::cargo::CommandCargoExt;
44
use std::{
55
collections::HashSet,
66
env::current_dir,
7-
fs::read_to_string,
7+
fs::{read_to_string, remove_file},
88
path::{Path, PathBuf},
99
process::Command,
10-
sync::Mutex,
10+
sync::{LazyLock, Mutex, MutexGuard},
1111
};
1212

13+
const SBPF_COVERAGE_DOWNLOAD_URL: &str =
14+
"https://github.com/trail-of-forks/sbpf-coverage/releases/download";
15+
16+
#[cfg(target_os = "linux")]
17+
const OS: &str = "Linux";
18+
19+
#[cfg(target_os = "macos")]
20+
const OS: &str = "macOS";
21+
22+
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
23+
compile_error!("Only Linux and macOS are supported.");
24+
1325
const BASIC_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/fixtures/basic");
1426
const MULTIPLE_TEST_CONFIGS_DIR: &str = concat!(
1527
env!("CARGO_MANIFEST_DIR"),
@@ -19,14 +31,18 @@ const EXTERNAL_CALL_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/fixtures/e
1931
const MULTIPLE_PROGRAMS_DIR: &str =
2032
concat!(env!("CARGO_MANIFEST_DIR"), "/fixtures/multiple_programs");
2133

34+
static AGAVE_TAG: LazyLock<String> = LazyLock::new(|| {
35+
read_to_string("agave_tag.txt")
36+
.map(|s| s.trim_end().to_owned())
37+
.unwrap()
38+
});
39+
2240
// smoelius: Only one Anchor test can be run at a time.
2341
static MUTEX: Mutex<()> = Mutex::new(());
2442

2543
#[test]
2644
fn basic() {
27-
let _lock = MUTEX.lock().unwrap();
28-
29-
yarn(BASIC_DIR).unwrap();
45+
let _lock = prepare_for_testing(BASIC_DIR).unwrap();
3046

3147
let mut command = anchor_coverage_command(BASIC_DIR);
3248
let status = command.status().unwrap();
@@ -53,9 +69,7 @@ fn basic() {
5369

5470
#[test]
5571
fn multiple_test_configs() {
56-
let _lock = MUTEX.lock().unwrap();
57-
58-
yarn(MULTIPLE_TEST_CONFIGS_DIR).unwrap();
72+
let _lock = prepare_for_testing(MULTIPLE_TEST_CONFIGS_DIR).unwrap();
5973

6074
for test_config in ["full", "just_increment_x", "just_increment_y"] {
6175
let mut command = anchor_coverage_command(MULTIPLE_TEST_CONFIGS_DIR);
@@ -133,9 +147,7 @@ fn multiple_test_configs() {
133147

134148
#[test]
135149
fn include_cargo_does_not_change_line_hits() {
136-
let _lock = MUTEX.lock().unwrap();
137-
138-
yarn(EXTERNAL_CALL_DIR).unwrap();
150+
let _lock = prepare_for_testing(EXTERNAL_CALL_DIR).unwrap();
139151

140152
let report_without_cargo = run_anchor_coverage_and_read_lcov(EXTERNAL_CALL_DIR, false).unwrap();
141153

@@ -159,9 +171,7 @@ fn include_cargo_does_not_change_line_hits() {
159171

160172
#[test]
161173
fn multiple_programs() {
162-
let _lock = MUTEX.lock().unwrap();
163-
164-
yarn(MULTIPLE_PROGRAMS_DIR).unwrap();
174+
let _lock = prepare_for_testing(MULTIPLE_PROGRAMS_DIR).unwrap();
165175

166176
let mut command = anchor_coverage_command(MULTIPLE_PROGRAMS_DIR);
167177
let status = command.status().unwrap();
@@ -201,6 +211,42 @@ fn multiple_programs() {
201211
}
202212
}
203213

214+
fn prepare_for_testing(dir: &str) -> Result<MutexGuard<'_, ()>> {
215+
download_patched_agave_tools(dir)?;
216+
217+
yarn(dir)?;
218+
219+
MUTEX.lock().map_err(|error| anyhow!("{error}"))
220+
}
221+
222+
fn download_patched_agave_tools(dir: impl AsRef<Path>) -> Result<()> {
223+
#[allow(clippy::redundant_pattern_matching)]
224+
if let Some(_) = patched_agave_tools(&dir)? {
225+
return Ok(());
226+
}
227+
228+
let filename = format!("patched-agave-tools-{}-{}.tar.gz", *AGAVE_TAG, OS);
229+
230+
let mut command = Command::new("wget");
231+
command.arg(format!(
232+
"{SBPF_COVERAGE_DOWNLOAD_URL}/{}/{}",
233+
*AGAVE_TAG, filename
234+
));
235+
command.current_dir(&dir);
236+
let status = command.status()?;
237+
ensure!(status.success(), "command failed: {command:?}");
238+
239+
let mut command = Command::new("tar");
240+
command.args(["xzf", &filename]);
241+
command.current_dir(&dir);
242+
let status = command.status()?;
243+
ensure!(status.success(), "command failed: {command:?}");
244+
245+
remove_file(dir.as_ref().join(filename))?;
246+
247+
Ok(())
248+
}
249+
204250
fn yarn(dir: &str) -> Result<()> {
205251
let mut command = Command::new("yarn");
206252
command.current_dir(dir);

src/util/mod.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use anyhow::Result;
1+
use anyhow::{bail, Result};
22
use std::{
33
env::current_dir,
44
ffi::OsStr,
@@ -20,6 +20,40 @@ pub fn files_with_extension(dir: impl AsRef<Path>, extension: &str) -> Result<Ve
2020
Ok(pcs_paths)
2121
}
2222

23+
pub fn patched_agave_tools(path: impl AsRef<Path>) -> Result<Option<PathBuf>> {
24+
let mut path_bufs = Vec::new();
25+
for result in read_dir(path)? {
26+
let entry = result?;
27+
let path = entry.path();
28+
let Some(file_name) = path.file_name() else {
29+
continue;
30+
};
31+
if file_name
32+
.to_str()
33+
.is_none_or(|s| !s.starts_with("patched-agave-tools-"))
34+
{
35+
continue;
36+
}
37+
if !path.is_dir() {
38+
eprintln!(
39+
"Warning: Found `{}` but it is not a directory. If it contains patched Agave \
40+
tools that you want to use, please unzip and untar it.",
41+
path.display()
42+
);
43+
continue;
44+
}
45+
path_bufs.push(path);
46+
}
47+
let mut iter = path_bufs.into_iter();
48+
let Some(path_buf) = iter.next() else {
49+
return Ok(None);
50+
};
51+
if iter.next().is_some() {
52+
bail!("Found multiple patched Agave tools directories");
53+
}
54+
Ok(Some(path_buf))
55+
}
56+
2357
pub trait StripCurrentDir {
2458
fn strip_current_dir(&self) -> &Self;
2559
}

0 commit comments

Comments
 (0)