Skip to content

Commit 16a826d

Browse files
committed
write run-test errors to output path on sandbox
1 parent a283dee commit 16a826d

File tree

3 files changed

+57
-7
lines changed

3 files changed

+57
-7
lines changed

tmc-langs-cli/README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ CLI interface for tmc-langs. The documentation for the various commands and para
22

33
## Environment variables
44

5-
| var name | usage |
6-
| ---------------------- | -------------------------- |
7-
| `TMC_LANGS_ROOT_URL` | Sets the TMC server url. |
8-
| `TMC_LANGS_CONFIG_DIR` | Sets the config directory. |
5+
| var name | usage |
6+
| ---------------------- | -------------------------------------------------------------- |
7+
| `TMC_LANGS_ROOT_URL` | Sets the TMC server url. |
8+
| `TMC_LANGS_CONFIG_DIR` | Sets the config directory. |
9+
| `TMC_SANDBOX` | If set, the CLI considers itself to be running in tmc-sandbox. |

tmc-langs-cli/src/error.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,10 @@ pub struct InvalidTokenError {
77
pub path: PathBuf,
88
pub source: anyhow::Error,
99
}
10+
11+
#[derive(Debug, Error)]
12+
#[error("Error running tests on sandbox")]
13+
pub struct SandboxTestError {
14+
pub path: Option<PathBuf>,
15+
pub source: anyhow::Error,
16+
}

tmc-langs-cli/src/main.rs

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ mod output;
77

88
use anyhow::{Context, Result};
99
use clap::{ArgMatches, Error, ErrorKind};
10-
use error::InvalidTokenError;
10+
use error::{InvalidTokenError, SandboxTestError};
1111
use output::{CombinedCourseData, ErrorData, Kind, Output, OutputData, OutputResult, Status};
1212
use serde::Serialize;
1313
use serde_json::Value as JsonValue;
1414
use std::collections::HashMap;
1515
use std::env;
1616
use std::fmt::Debug;
1717
use std::fs::{self, File};
18+
use std::io::Write;
1819
use std::path::{Path, PathBuf};
1920
use tempfile::NamedTempFile;
2021
use tmc_langs_core::oauth2::{
@@ -45,6 +46,7 @@ fn main() {
4546
let causes: Vec<String> = e.chain().map(|e| format!("Caused by: {}", e)).collect();
4647
let message = error_message_special_casing(&e);
4748
let kind = solve_error_kind(&e);
49+
let sandbox_path = check_sandbox_err(&e);
4850
let error_output = Output::OutputData(OutputData {
4951
status: Status::Finished,
5052
message: Some(message),
@@ -55,7 +57,7 @@ fn main() {
5557
}),
5658
percent_done: 1.0,
5759
});
58-
if let Err(err) = print_output(&error_output, pretty) {
60+
if let Err(err) = print_output_with_file(&error_output, pretty, sandbox_path) {
5961
// the above function shouldn't fail ever, but in theory some data could
6062
// have a flawed Serialize implementation, so better safe than sorry
6163
let output = Output::OutputData::<()>(OutputData {
@@ -120,6 +122,20 @@ fn error_message_special_casing(e: &anyhow::Error) -> String {
120122
e.to_string()
121123
}
122124

125+
/// Goes through the error chain and returns the error output file path if a sandbox test error is found
126+
fn check_sandbox_err(e: &anyhow::Error) -> Option<PathBuf> {
127+
for cause in e.chain() {
128+
// command not found errors are special cased to notify the user that they may need to install additional software
129+
if let Some(SandboxTestError {
130+
path: Some(path), ..
131+
}) = cause.downcast_ref::<SandboxTestError>()
132+
{
133+
return Some(path.clone());
134+
}
135+
}
136+
None
137+
}
138+
123139
fn run_app(matches: ArgMatches, pretty: bool) -> Result<()> {
124140
// enforces that each branch must return a PrintToken as proof of having printed the output
125141
let _printed: PrintToken = match matches.subcommand() {
@@ -622,7 +638,18 @@ fn run_app(matches: ArgMatches, pretty: bool) -> Result<()> {
622638
"Failed to run tests for exercise at {}",
623639
exercise_path.display()
624640
)
625-
})?;
641+
});
642+
643+
let test_result = if env::var("TMC_SANDBOX").is_ok() {
644+
// in sandbox, wrap error to signal we want to write the output into a file
645+
test_result.map_err(|e| SandboxTestError {
646+
path: output_path.map(Path::to_path_buf),
647+
source: e,
648+
})?
649+
} else {
650+
// not in sandbox, just unwrap
651+
test_result?
652+
};
626653

627654
if let Some(output_path) = output_path {
628655
write_result_to_file_as_json(&test_result, output_path)?;
@@ -1442,13 +1469,28 @@ fn run_settings(matches: &ArgMatches, pretty: bool) -> Result<PrintToken> {
14421469
}
14431470

14441471
fn print_output<T: Serialize + Debug>(output: &Output<T>, pretty: bool) -> Result<PrintToken> {
1472+
print_output_with_file(output, pretty, None)
1473+
}
1474+
1475+
fn print_output_with_file<T: Serialize + Debug>(
1476+
output: &Output<T>,
1477+
pretty: bool,
1478+
path: Option<PathBuf>,
1479+
) -> Result<PrintToken> {
14451480
let result = if pretty {
14461481
serde_json::to_string_pretty(&output)
14471482
} else {
14481483
serde_json::to_string(&output)
14491484
}
14501485
.with_context(|| format!("Failed to convert {:?} to JSON", output))?;
14511486
println!("{}", result);
1487+
1488+
if let Some(path) = path {
1489+
let mut file = File::create(&path)
1490+
.with_context(|| format!("Failed to open file at {}", path.display()))?;
1491+
file.write_all(result.as_bytes())
1492+
.with_context(|| format!("Failed to write result to {}", path.display()))?;
1493+
}
14521494
Ok(PrintToken)
14531495
}
14541496

0 commit comments

Comments
 (0)