Skip to content

Commit 705b86d

Browse files
authored
Merge pull request #1895 from EliahKagan/run-ci/s390x
Set up `cross` for limited but substantial local testing of s390x and Android
2 parents de2f97d + 73dcf57 commit 705b86d

File tree

11 files changed

+227
-5
lines changed

11 files changed

+227
-5
lines changed

etc/docker/Dockerfile.test-cross

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
ARG TARGET
2+
FROM ghcr.io/cross-rs/${TARGET}:latest
3+
4+
ARG TARGET
5+
COPY customize.sh /usr/local/bin/
6+
RUN chmod +x /usr/local/bin/customize.sh && \
7+
/usr/local/bin/customize.sh "$TARGET"
+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#!/bin/bash
2+
set -euxC
3+
4+
target="$1"
5+
test -n "$target"
6+
7+
# Arrange for the indirect `tzdata` dependency to be installed and configured
8+
# without prompting for the time zone. (Passing `-y` is not enough.)
9+
export DEBIAN_FRONTEND=noninteractive TZ=UTC
10+
11+
# Install tools for setting up APT repositores. Install `apt-utils` before the
12+
# others, so the installation of `gnupg` can use it for debconf.
13+
apt-get update
14+
apt-get install --no-install-recommends -y apt-utils
15+
apt-get install --no-install-recommends -y apt-transport-https dpkg-dev gnupg
16+
type dpkg-architecture # Make sure we really have this.
17+
18+
# Decide what architecture to use for `git`, shared libraries `git` links to,
19+
# and shared libraries gitoxide links to when building `max`. Instead of this
20+
# custom logic, we could use `$CROSS_DEB_ARCH`, which `cross` tries to provide
21+
# (https://github.com/cross-rs/cross/blob/v0.2.5/src/lib.rs#L268), and which is
22+
# available for roughly the same architectures where this logic gets a nonempty
23+
# value. But using `$CROSS_DEB_ARCH` may make it harder to build and test the
24+
# image manually. In particular, if it is not passed, we would conclude that we
25+
# should install the versions of those packages with the host's architecture.
26+
apt_suffix=
27+
if target_arch="$(dpkg-architecture --host-type "$target" --query DEB_HOST_ARCH)"
28+
then
29+
dpkg --add-architecture "$target_arch"
30+
apt_suffix=":$target_arch"
31+
printf 'INFO: Using target architecture for `git` and libs in container.\n'
32+
printf 'INFO: This architecture is %s.\n' "$target_arch"
33+
else
34+
apt_suffix=''
35+
printf 'WARNING: Using HOST architecture for `git` and libs in container.\n'
36+
fi
37+
38+
# Get release codename. Like `lsb_release -sc`. (`lsb_release` may be absent.)
39+
release="$(sed -n 's/^VERSION_CODENAME=//p' /etc/os-release)"
40+
41+
# Add the git-core PPA manually. (Faster than installing `add-apt-repository`.)
42+
echo "deb https://ppa.launchpadcontent.net/git-core/ppa/ubuntu $release main" \
43+
>/etc/apt/sources.list.d/git-core-ubuntu-ppa.list
44+
apt-key adv --keyserver keyserver.ubuntu.com \
45+
--recv-keys F911AB184317630C59970973E363C90F8F1B6217
46+
apt-get update
47+
48+
# Remove the old `git` and associated packages.
49+
apt-get purge --autoremove -y git
50+
51+
# Git dependencies. These are for the desired architecture, except `git-man` is
52+
# the same package for all architectures, and we can't always install `perl` or
53+
# `liberror-perl` for the desired architecture (at least in s390x).
54+
# TODO(maint): Resolve these dynamically to support future `cross` base images.
55+
git_deps=(
56+
git-man
57+
"libc6$apt_suffix"
58+
"libcurl3-gnutls$apt_suffix"
59+
"libexpat1$apt_suffix"
60+
liberror-perl
61+
"libpcre2-8-0$apt_suffix"
62+
"zlib1g$apt_suffix"
63+
perl
64+
)
65+
66+
# Other dependencies for running the gitoxide test suite and fixture scripts,
67+
# and for building and testing gitoxide for feature sets beyond `max-pure`.
68+
gix_test_deps=(
69+
ca-certificates
70+
cmake
71+
"curl$apt_suffix"
72+
jq
73+
"libc-dev$apt_suffix"
74+
"libssl-dev$apt_suffix"
75+
patch
76+
pkgconf
77+
)
78+
79+
if test -n "$apt_suffix"; then
80+
# Install everything we need except `git` (and what we already have). We
81+
# can't necessarily install `git` this way, because it insists on `perl`
82+
# and `liberror-perl` dependencies of the same architecture as it. These
83+
# may not be possible to install in a mixed environment, where most
84+
# packages are a different architecture, and where `perl` is a dependency
85+
# of other important packages. So we will install everything else first
86+
# (then manually add `git`).
87+
apt-get install --no-install-recommends -y \
88+
"${git_deps[@]}" "${gix_test_deps[@]}" file
89+
90+
# Add `git` by manually downloading it and installing it with `dpkg`,
91+
# forcing installation to proceed even if its `perl` and `liberror-perl`
92+
# dependencies, as declared by `git`, are absent. (We have already
93+
# installed them, but in a possibly different architecture. `git` can still
94+
# use them, because its use is to run scripts, rather than to link to a
95+
# shared library they provide.) It is preferred to let `apt-get download`
96+
# drop privileges to the `_apt` user during download, so we download it
97+
# inside `/tmp`. But we create a subdirectory so it is safe to make
98+
# assumptions about what files globs can expand to, even if `/tmp` is
99+
# mounted to an outside share temp dir on a multi-user system.
100+
mkdir /tmp/dl # Don't use `-p`; if it exists already, we cannot trust it.
101+
chown _apt /tmp/dl # Use owner, as the container may have no `_apt` group.
102+
(cd /tmp/dl && apt-get download "git$apt_suffix")
103+
dpkg --ignore-depends="perl$apt_suffix,liberror-perl$apt_suffix" \
104+
-i /tmp/dl/git[-_]*.deb
105+
rm -r /tmp/dl
106+
else
107+
# Install everything we need, including `git`.
108+
apt-get install --no-install-recommends -y git "${gix_test_deps[@]}" file
109+
fi
110+
111+
# Show information about the newly installed `git` (and ensure it can run).
112+
git version --build-options
113+
git="$(command -v git)"
114+
file -- "$git"
115+
116+
# Clean up files related to package management that we won't need anymore.
117+
apt-get clean
118+
rm -rf /var/lib/apt/lists/*
119+
120+
# If this image has a runner script `cross` uses for Android, patch the script
121+
# to add the ability to suppress its customization of `LD_PRELOAD`. The runner
122+
# script sets `LD_PRELOAD` to the path of `libc++_shared.so` in the Android NDK
123+
# (https://github.com/cross-rs/cross/blob/v0.2.5/docker/android-runner#L34).
124+
# But this causes a problem for us. When a host-architecture program is run,
125+
# `ld.so` shows a message about the "wrong ELF class". Such programs can still
126+
# run, but when we rely on their specific output to stderr, fixtures and tests
127+
# fail. The change we make here lets us set `NO_PRELOAD_CXX=1` to avoid that.
128+
runner=/android-runner
129+
patch='s/^[[:blank:]]*export LD_PRELOAD=/test "${NO_PRELOAD_CXX:-0}" != 0 || &/'
130+
if test -f "$runner"; then sed -i.orig "$patch" -- "$runner"; fi
131+
132+
# Ensure a nonempty Git `system` scope (for the `installation_config` tests).
133+
git config --system gitoxide.imaginary.arbitraryVariable arbitraryValue

etc/docker/test-cross.toml

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# `cross` configuration for running tests. Treated like `Cross.toml` if enabled
2+
# with `CROSS_CONFIG=etc/docker/test-cross.toml`. This avoids affecting other
3+
# `cross` usage, e.g. in `release.yml`. See `cross-test` recipes in `justfile`.
4+
5+
[build.env]
6+
passthrough = [
7+
"CI",
8+
"GITHUB_ACTIONS",
9+
"GIX_CREDENTIALS_HELPER_STDERR",
10+
"GIX_EXTERNAL_COMMAND_STDERR",
11+
"GIX_OBJECT_CACHE_MEMORY",
12+
"GIX_PACK_CACHE_MEMORY",
13+
"GIX_TEST_CREATE_ARCHIVES_EVEN_ON_CI",
14+
"GIX_TEST_EXPECT_REDUCED_TRUST",
15+
"GIX_TEST_IGNORE_ARCHIVES",
16+
"GIX_VERSION",
17+
"NO_PRELOAD_CXX",
18+
"RUST_BACKTRACE",
19+
"RUST_LIB_BACKTRACE",
20+
]
21+
22+
[target.armv7-linux-androideabi]
23+
image = "cross-rs-gitoxide:armv7-linux-androideabi"
24+
25+
[target.s390x-unknown-linux-gnu]
26+
image = "cross-rs-gitoxide:s390x-unknown-linux-gnu"

gix-config-value/tests/value/path.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -89,16 +89,16 @@ mod interpolate {
8989
Ok(())
9090
}
9191

92-
#[cfg(windows)]
92+
#[cfg(any(target_os = "windows", target_os = "android"))]
9393
#[test]
94-
fn tilde_with_given_user_is_unsupported_on_windows() {
94+
fn tilde_with_given_user_is_unsupported_on_windows_and_android() {
9595
assert!(matches!(
9696
interpolate_without_context("~baz/foo/bar"),
9797
Err(gix_config_value::path::interpolate::Error::UserInterpolationUnsupported)
9898
));
9999
}
100100

101-
#[cfg(not(windows))]
101+
#[cfg(not(any(target_os = "windows", target_os = "android")))]
102102
#[test]
103103
fn tilde_with_given_user() -> crate::Result {
104104
let home = std::env::current_dir()?;

justfile

+19
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,25 @@ journey-tests-async: dbg
224224
cargo build -p gix-testtools
225225
dbg="$({{ j }} dbg)" && tests/journey.sh "$dbg/ein" "$dbg/gix" "$dbg/jtt" async
226226

227+
# Build a customized `cross` container image for testing
228+
cross-image target:
229+
docker build --build-arg "TARGET={{ target }}" \
230+
-t "cross-rs-gitoxide:{{ target }}" \
231+
-f etc/docker/Dockerfile.test-cross etc/docker/test-cross-context
232+
233+
# Test another platform with `cross`
234+
cross-test target options test-options: (cross-image target)
235+
CROSS_CONFIG=etc/docker/test-cross.toml NO_PRELOAD_CXX=1 \
236+
cross test --workspace --no-fail-fast --target {{ target }} \
237+
{{ options }} -- --skip realpath::fuzzed_timeout {{ test-options }}
238+
239+
# Test s390x with `cross`
240+
cross-test-s390x: (cross-test 's390x-unknown-linux-gnu' '' '')
241+
242+
# Test Android with `cross` (max-pure)
243+
cross-test-android: (cross-test 'armv7-linux-androideabi'
244+
'--no-default-features --features max-pure' '')
245+
227246
# Run `cargo diet` on all crates to see that they are still in bounds
228247
check-size:
229248
etc/check-package-size.sh

tests/it/src/args.rs

+16
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,22 @@ pub enum Subcommands {
7373
/// current repository. Its main use is checking that fixture scripts are have correct modes.
7474
#[clap(visible_alias = "cm")]
7575
CheckMode {},
76+
/// Print environment variables as `NAME=value` lines.
77+
///
78+
/// It is useful to be able to observe environment variables that are set when running code
79+
/// with tools such as `cargo` or `cross`. Commands like `cargo run -p internal-tools -- env`
80+
/// include environment changes from `cargo` itself. With `cross`, changes are more extensive,
81+
/// due to effects of `build.env.passthrough`, container customization, and preexisting special
82+
/// cases in wrapper scripts shipped in default `cross` containers (such as to `LD_PRELOAD`).
83+
///
84+
/// Since one use for checking environment variables is to investigate the effects of
85+
/// environments that contain variable names or values that are not valid Unicode, this avoids
86+
/// requiring that environment variables all be Unicode. Any name or value that is not Unicode
87+
/// is shown in its Rust debug representation. This is always quoted. To decrease ambiguity,
88+
/// any name or value containing a literal double quote or newline is also shown in its debug
89+
/// representation. Names and values without such content are shown literally and not quoted.
90+
#[clap(visible_alias = "e")]
91+
Env {},
7692
}
7793

7894
#[derive(Clone)]

tests/it/src/commands/env.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
pub(super) mod function {
2+
pub fn env() -> anyhow::Result<()> {
3+
for (name, value) in std::env::vars_os() {
4+
println!("{}={}", repr(&name), repr(&value));
5+
}
6+
Ok(())
7+
}
8+
9+
fn repr(text: &std::ffi::OsStr) -> String {
10+
text.to_str()
11+
.filter(|s| !s.chars().any(|c| c == '"' || c == '\n'))
12+
.map_or_else(|| format!("{text:?}"), ToOwned::to_owned)
13+
}
14+
}

tests/it/src/commands/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ pub use git_to_sh::function::git_to_sh;
66

77
pub mod check_mode;
88
pub use check_mode::function::check_mode;
9+
10+
pub mod env;
11+
pub use env::function::env;

tests/it/src/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ fn main() -> anyhow::Result<()> {
3232
patterns,
3333
} => commands::copy_royal(dry_run, &worktree_root, destination_dir, patterns),
3434
Subcommands::CheckMode {} => commands::check_mode(),
35+
Subcommands::Env {} => commands::env(),
3536
}
3637
}
3738

tests/tools/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -992,7 +992,7 @@ pub fn umask() -> u32 {
992992
.output()
993993
.expect("can execute `sh -c umask`");
994994
assert!(output.status.success(), "`sh -c umask` failed");
995-
assert!(output.stderr.is_empty(), "`sh -c umask` unexpected message");
995+
assert_eq!(output.stderr.as_bstr(), "", "`sh -c umask` unexpected message");
996996
let text = output.stdout.to_str().expect("valid Unicode").trim();
997997
u32::from_str_radix(text, 8).expect("parses as octal number")
998998
}

tests/tools/tests/umask.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#[test]
22
#[cfg(unix)]
3-
#[cfg_attr(not(target_os = "linux"), ignore = "The test itself uses /proc")]
3+
#[cfg_attr(
4+
not(any(target_os = "linux", target_os = "android")),
5+
ignore = "The test itself uses /proc"
6+
)]
47
fn umask() {
58
use std::fs::File;
69
use std::io::{BufRead, BufReader};

0 commit comments

Comments
 (0)