Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom cabal/ghc arguments in test harness #140

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

5 changes: 0 additions & 5 deletions src/ghci/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,6 @@ use self::parse::parse_eval_commands;
/// private-use-area codepoints or something in the future.
pub const PROMPT: &str = "###~GHCIWATCH-PROMPT~###";

/// The name we import `System.IO` as in `ghci`. This is used to run a few `putStrLn` commands and
/// similar without messing with the user's namespace. If you have a module in your project named
/// `GHCIWATCH_IO_INTERNAL__` that's on you.
pub const IO_MODULE_NAME: &str = "GHCIWATCH_IO_INTERNAL__";

/// Options for constructing a [`Ghci`]. This is like a lower-effort builder interface, mostly provided
/// because Rust tragically lacks named arguments.
///
Expand Down
6 changes: 0 additions & 6 deletions src/ghci/stdin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ use super::parse::ShowPaths;
use super::stderr::StderrEvent;
use super::GhciCommand;
use super::Mode;
use super::IO_MODULE_NAME;
use super::PROMPT;
use crate::ghci::GhciStdout;

Expand Down Expand Up @@ -98,11 +97,6 @@ impl GhciStdin {
self.set_mode(stdout, Mode::Internal).await?;
self.write_line(stdout, &format!(":set prompt-cont {PROMPT}\n"))
.await?;
self.write_line(
stdout,
&format!("import qualified System.IO as {IO_MODULE_NAME}\n"),
)
.await?;

for command in setup_commands {
tracing::debug!(%command, "Running after-startup command");
Expand Down
1 change: 1 addition & 0 deletions test-harness/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ nix = { version = "0.26.2", default_features = false, features = ["process", "si
regex = "1.9.4"
serde = { version = "1.0.186", features = ["derive"] }
serde_json = "1.0.105"
shell-words = "1.1.0"
tempfile = "3.8.0"
test-harness-macro = { path = "../test-harness-macro" }
test_bin = "0.4.0"
Expand Down
23 changes: 23 additions & 0 deletions test-harness/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,29 @@ impl Fs {
file.write_all(data.as_ref()).await.into_diagnostic()
}

/// Prepend some data to a path.
#[tracing::instrument(skip(data))]
pub async fn prepend(
&self,
path: impl AsRef<Path> + Debug,
data: impl AsRef<[u8]>,
) -> miette::Result<()> {
let path = path.as_ref();
let contents = self.read(path).await?;
let mut file = File::create(path)
.await
.into_diagnostic()
.wrap_err_with(|| format!("Failed to open {path:?}"))?;
file.write_all(data.as_ref())
.await
.into_diagnostic()
.wrap_err_with(|| format!("Failed to write {path:?}"))?;
file.write_all(contents.as_ref())
.await
.into_diagnostic()
.wrap_err_with(|| format!("Failed to write {path:?}"))
}

/// Read a path into a string.
#[tracing::instrument]
pub async fn read(&self, path: impl AsRef<Path> + Debug) -> miette::Result<String> {
Expand Down
54 changes: 47 additions & 7 deletions test-harness/src/ghciwatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub(crate) const LOG_FILENAME: &str = "ghciwatch.json";
pub struct GhciWatchBuilder {
project_directory: PathBuf,
args: Vec<OsString>,
cabal_args: Vec<String>,
#[allow(clippy::type_complexity)]
before_start: Option<Box<dyn FnOnce(PathBuf) -> BoxFuture<'static, miette::Result<()>> + Send>>,
default_timeout: Duration,
Expand All @@ -43,6 +44,7 @@ impl GhciWatchBuilder {
Self {
project_directory: project_directory.as_ref().to_owned(),
args: Default::default(),
cabal_args: Default::default(),
before_start: None,
default_timeout: Duration::from_secs(10),
startup_timeout: Duration::from_secs(60),
Expand All @@ -62,6 +64,34 @@ impl GhciWatchBuilder {
self
}

/// Add an argument to the `cabal repl` invocation.
pub fn with_cabal_arg(mut self, arg: impl AsRef<str>) -> Self {
self.cabal_args.push(arg.as_ref().to_owned());
self
}

/// Add multiple arguments to the `cabal repl` invocation.
pub fn with_cabal_args(mut self, args: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
self.cabal_args
.extend(args.into_iter().map(|s| s.as_ref().to_owned()));
self
}

/// Add a GHC argument to the `cabal repl` invocation.
pub fn with_ghc_arg(mut self, arg: impl AsRef<str>) -> Self {
self.cabal_args.push("--repl-option".into());
self.cabal_args.push(arg.as_ref().to_owned());
self
}

/// Add multiple GHC arguments to the `cabal repl` invocation.
pub fn with_ghc_args(mut self, args: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
for arg in args {
self = self.with_ghc_arg(arg);
}
self
}

/// Add a hook to run after project files are copied to the temporary directory but before
/// `ghciwatch` is started.
pub fn before_start<F>(mut self, before_start: impl Fn(PathBuf) -> F + Send + 'static) -> Self
Expand Down Expand Up @@ -153,22 +183,31 @@ impl GhciWatch {
let log_path = tempdir.join(LOG_FILENAME);

tracing::info!("Starting ghciwatch");
let ghc_options = vec!["-fdiagnostics-color=always"];
let cabal_command = shell_words::join(
[
"cabal",
"--offline",
&format!("--with-compiler=ghc-{full_ghc_version}"),
"-flocal-dev",
]
.into_iter()
.chain(ghc_options.into_iter().flat_map(|s| ["--repl-option", s]))
.chain(builder.cabal_args.iter().map(|s| s.as_str()))
.chain(["v2-repl", "lib:test-dev"]),
);

let mut command = Command::new(test_bin::get_test_bin("ghciwatch").get_program());
command
.arg("--log-json")
.arg(&log_path)
.args([
"--command",
&format!(
"cabal --offline --with-compiler=ghc-{full_ghc_version} -flocal-dev --repl-option -fdiagnostics-color=always v2-repl lib:test-dev"
),
&cabal_command,
"--before-startup-shell",
"hpack --force .",
"--tracing-filter",
&[
"ghciwatch::watcher=trace",
"ghciwatch=debug",
].join(","),
&["ghciwatch::watcher=trace", "ghciwatch=debug"].join(","),
"--trace-spans",
"new,close",
"--poll",
Expand All @@ -186,6 +225,7 @@ impl GhciWatch {
.stdout(Stdio::inherit())
.kill_on_drop(true);

tracing::info!("Starting ghciwatch");
let mut child = command
.spawn()
.into_diagnostic()
Expand Down