Skip to content

Commit 20103ad

Browse files
committed
plugin refactor
1 parent 619f390 commit 20103ad

File tree

31 files changed

+387
-465
lines changed

31 files changed

+387
-465
lines changed

plugins/csharp/src/error.rs

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,10 @@
11
//! Error type for C#
22
use std::path::PathBuf;
3-
use std::process::ExitStatus;
43
use thiserror::Error;
5-
use tmc_langs_framework::{error::CommandNotFound, zip, TmcError};
4+
use tmc_langs_framework::{error::CommandError, error::FileIo, zip, TmcError};
65

76
#[derive(Debug, Error)]
87
pub enum CSharpError {
9-
#[error("Failed to create file {0}")]
10-
CreateFile(PathBuf, #[source] std::io::Error),
11-
#[error("Failed to write file {0}")]
12-
WriteFile(PathBuf, #[source] std::io::Error),
13-
#[error("Failed to read file {0}")]
14-
ReadFile(PathBuf, #[source] std::io::Error),
15-
#[error("Failed to remove file {0}")]
16-
RemoveFile(PathBuf, #[source] std::io::Error),
17-
#[error("Failed to create dir {0}")]
18-
CreateDir(PathBuf, #[source] std::io::Error),
19-
#[error("Failed to remove dir {0}")]
20-
RemoveDir(PathBuf, #[source] std::io::Error),
21-
228
#[error("Failed to parse exercise description at {0}")]
239
ParseExerciseDesc(PathBuf, #[source] serde_json::Error),
2410
#[error("Failed to parse test results at {0}")]
@@ -30,13 +16,13 @@ pub enum CSharpError {
3016
MissingBootstrapDll(PathBuf),
3117
#[error("Error while handling tmc-csharp-runner zip")]
3218
Zip(#[source] zip::result::ZipError),
33-
#[error("Failed to run {0}")]
34-
RunFailed(&'static str, #[source] std::io::Error),
35-
#[error("Command {0} failed with return code {1}")]
36-
CommandFailed(&'static str, ExitStatus),
3719

3820
#[error("Command not found")]
39-
CommandNotFound(#[from] CommandNotFound),
21+
Command(#[from] CommandError),
22+
#[error("File IO error")]
23+
FileIo(#[from] FileIo),
24+
#[error("Error")]
25+
Tmc(#[from] TmcError),
4026
}
4127

4228
impl From<CSharpError> for TmcError {

plugins/csharp/src/plugin.rs

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use tmc_langs_framework::{
1010
domain::{
1111
ExerciseDesc, RunResult, RunStatus, Strategy, TestDesc, TestResult, ValidationResult,
1212
},
13+
error::FileIo,
14+
io::file_util,
1315
plugin::Language,
1416
zip::ZipArchive,
1517
LanguagePlugin, TmcError,
@@ -18,8 +20,8 @@ use tmc_langs_framework::{
1820
use std::collections::HashMap;
1921
use std::env;
2022
use std::ffi::{OsStr, OsString};
21-
use std::fs::{self, File};
22-
use std::io::{BufReader, Cursor, Read, Seek, Write};
23+
use std::fs::File;
24+
use std::io::{BufReader, Cursor, Read, Seek};
2325
use std::path::{Path, PathBuf};
2426
use std::time::Duration;
2527
use walkdir::WalkDir;
@@ -44,18 +46,13 @@ impl CSharpPlugin {
4446
if file.is_file() {
4547
let target_file_path = target.join(file.sanitized_name());
4648
if let Some(parent) = target_file_path.parent() {
47-
fs::create_dir_all(&parent)
48-
.map_err(|e| CSharpError::CreateDir(target_file_path.clone(), e))?;
49+
file_util::create_dir_all(&parent)?;
4950
}
50-
let mut target_file = File::create(&target_file_path)
51-
.map_err(|e| CSharpError::CreateFile(target_file_path.clone(), e))?;
5251
let bytes: Vec<u8> = file
5352
.bytes()
5453
.collect::<Result<Vec<_>, _>>()
55-
.map_err(|e| CSharpError::ReadFile(target_file_path.clone(), e))?;
56-
target_file
57-
.write_all(&bytes)
58-
.map_err(|e| CSharpError::WriteFile(target_file_path, e))?;
54+
.map_err(|e| FileIo::FileRead(target_file_path.clone(), e))?;
55+
file_util::write_to_file(&mut bytes.as_slice(), target_file_path)?;
5956
}
6057
}
6158
Ok(())
@@ -94,8 +91,7 @@ impl CSharpPlugin {
9491

9592
/// Parses the test results JSON file at the path argument.
9693
fn parse_test_results(test_results_path: &Path) -> Result<RunResult, CSharpError> {
97-
let test_results = File::open(test_results_path)
98-
.map_err(|e| CSharpError::ReadFile(test_results_path.to_path_buf(), e))?;
94+
let test_results = file_util::open_file(test_results_path)?;
9995
let test_results: Vec<CSTestResult> = serde_json::from_reader(test_results)
10096
.map_err(|e| CSharpError::ParseTestResults(test_results_path.to_path_buf(), e))?;
10197

@@ -122,14 +118,13 @@ impl LanguagePlugin for CSharpPlugin {
122118
// checks the directories in src for csproj files
123119
fn is_exercise_type_correct(path: &Path) -> bool {
124120
WalkDir::new(path.join("src"))
125-
.min_depth(2)
126121
.max_depth(2)
127122
.into_iter()
128123
.filter_map(|e| e.ok())
129124
.any(|e| e.path().extension() == Some(&OsString::from("csproj")))
130125
}
131126

132-
/// Finds .csproj files and checks whether they are in a X/src/ directory, returning X if so.
127+
/// Finds any directory X which contains a X/src/*.csproj file.
133128
fn find_project_dir_in_zip<R: Read + Seek>(
134129
zip_archive: &mut ZipArchive<R>,
135130
) -> Result<PathBuf, TmcError> {
@@ -155,21 +150,16 @@ impl LanguagePlugin for CSharpPlugin {
155150

156151
// runs --generate-points-file and parses the generated .tmc_available_points.json
157152
fn scan_exercise(&self, path: &Path, exercise_name: String) -> Result<ExerciseDesc, TmcError> {
158-
let mut command = TmcCommand::new("dotnet");
153+
let mut command = TmcCommand::new("dotnet".to_string());
159154
command
160155
.current_dir(path)
161156
.arg(Self::get_bootstrap_path()?)
162157
.arg("--generate-points-file");
163-
let output = command.output()?;
164-
log::trace!("stdout: {}", String::from_utf8_lossy(&output.stdout));
165-
log::debug!("stderr: {}", String::from_utf8_lossy(&output.stderr));
166-
if !output.status.success() {
167-
return Err(CSharpError::CommandFailed("dotnet", output.status).into());
168-
}
158+
command.output_checked()?;
169159

170160
let exercise_desc_json_path = path.join(".tmc_available_points.json");
171161
let exercise_desc_json = File::open(&exercise_desc_json_path)
172-
.map_err(|e| CSharpError::ReadFile(exercise_desc_json_path.clone(), e))?;
162+
.map_err(|e| FileIo::FileRead(exercise_desc_json_path.clone(), e))?;
173163
let json: HashMap<String, Vec<String>> =
174164
serde_json::from_reader(BufReader::new(exercise_desc_json))
175165
.map_err(|e| CSharpError::ParseExerciseDesc(exercise_desc_json_path, e))?;
@@ -192,10 +182,9 @@ impl LanguagePlugin for CSharpPlugin {
192182
) -> Result<RunResult, TmcError> {
193183
let test_results_path = path.join(".tmc_test_results.json");
194184
if test_results_path.exists() {
195-
fs::remove_file(&test_results_path)
196-
.map_err(|e| CSharpError::RemoveFile(test_results_path.clone(), e))?;
185+
file_util::remove_file(&test_results_path)?;
197186
}
198-
let mut command = TmcCommand::new("dotnet");
187+
let mut command = TmcCommand::new("dotnet".to_string());
199188
command
200189
.current_dir(path)
201190
.arg(Self::get_bootstrap_path()?)
@@ -257,16 +246,14 @@ impl LanguagePlugin for CSharpPlugin {
257246
fn clean(&self, path: &Path) -> Result<(), TmcError> {
258247
let test_results_path = path.join(".tmc_test_results.json");
259248
if test_results_path.exists() {
260-
fs::remove_file(&test_results_path)
261-
.map_err(|e| CSharpError::RemoveFile(test_results_path.clone(), e))?;
249+
file_util::remove_file(&test_results_path)?;
262250
}
263251
for entry in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
264252
let file_name = entry.path().file_name();
265253
if file_name == Some(&OsString::from("bin"))
266254
|| file_name == Some(&OsString::from("obj"))
267255
{
268-
fs::remove_dir_all(entry.path())
269-
.map_err(|e| CSharpError::RemoveDir(entry.path().to_path_buf(), e))?;
256+
file_util::remove_dir_all(entry.path())?;
270257
}
271258
}
272259
Ok(())
@@ -276,6 +263,7 @@ impl LanguagePlugin for CSharpPlugin {
276263
#[cfg(test)]
277264
mod test {
278265
use super::*;
266+
use std::fs;
279267
use std::sync::Once;
280268
use tempfile::TempDir;
281269

plugins/java/src/ant.rs

Lines changed: 17 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,13 @@ use super::{error::JavaError, plugin::JavaPlugin, CompileResult, TestRun, SEPARA
77
use j4rs::Jvm;
88
use policy::AntStudentFilePolicy;
99
use std::env;
10-
use std::fs::{self, File};
11-
use std::io::Write;
1210
use std::path::Path;
1311
use std::process::Stdio;
1412
use std::time::Duration;
1513
use tmc_langs_framework::{
1614
command::TmcCommand,
1715
domain::{ExerciseDesc, RunResult, ValidationResult},
16+
io::file_util,
1817
plugin::{Language, LanguagePlugin},
1918
TmcError,
2019
};
@@ -36,7 +35,7 @@ impl AntPlugin {
3635

3736
fn get_ant_executable(&self) -> &'static str {
3837
if cfg!(windows) {
39-
if let Ok(status) = TmcCommand::new("ant")
38+
if let Ok(status) = TmcCommand::new("ant".to_string())
4039
.arg("-version")
4140
.stdout(Stdio::piped())
4241
.stderr(Stdio::piped())
@@ -61,13 +60,8 @@ impl AntPlugin {
6160

6261
// TODO: don't traverse symlinks
6362
if !runner_path.exists() {
64-
fs::create_dir_all(&runner_dir).map_err(|e| JavaError::DirCreate(runner_dir, e))?;
6563
log::debug!("writing tmc-junit-runner to {}", runner_path.display());
66-
let mut target_file = File::create(&runner_path)
67-
.map_err(|e| JavaError::FileCreate(runner_path.clone(), e))?;
68-
target_file
69-
.write_all(JUNIT_RUNNER_ARCHIVE)
70-
.map_err(|e| JavaError::JarWrite(runner_path, e))?;
64+
file_util::write_to_file(&mut JUNIT_RUNNER_ARCHIVE, &runner_path)?;
7165
} else {
7266
log::debug!("already exists");
7367
}
@@ -118,34 +112,21 @@ impl LanguagePlugin for AntPlugin {
118112
log::debug!("Cleaning project at {}", path.display());
119113

120114
let stdout_path = path.join("build_log.txt");
121-
let stdout = File::create(&stdout_path)
122-
.map_err(|e| JavaError::FileCreate(stdout_path.clone(), e))?;
115+
let stdout = file_util::create_file(&stdout_path)?;
123116
let stderr_path = path.join("build_errors.txt");
124-
let stderr = File::create(&stderr_path)
125-
.map_err(|e| JavaError::FileCreate(stderr_path.clone(), e))?;
117+
let stderr = file_util::create_file(&stderr_path)?;
126118

127119
let ant_exec = self.get_ant_executable();
128-
let mut command = TmcCommand::new(ant_exec);
120+
let mut command = TmcCommand::new(ant_exec.to_string());
129121
command
130122
.arg("clean")
131123
.stdout(stdout)
132124
.stderr(stderr)
133125
.current_dir(path);
134-
let output = command.output()?;
135-
136-
if output.status.success() {
137-
fs::remove_file(&stdout_path).map_err(|e| JavaError::FileRemove(stdout_path, e))?;
138-
fs::remove_file(&stderr_path).map_err(|e| JavaError::FileRemove(stderr_path, e))?;
139-
Ok(())
140-
} else {
141-
Err(JavaError::FailedCommand(
142-
"ant clean".to_string(),
143-
output.status,
144-
String::from_utf8_lossy(&output.stdout).into_owned(),
145-
String::from_utf8_lossy(&output.stderr).into_owned(),
146-
)
147-
.into())
148-
}
126+
command.output_checked()?;
127+
file_util::remove_file(&stdout_path)?;
128+
file_util::remove_file(&stderr_path)?;
129+
Ok(())
149130
}
150131
}
151132

@@ -192,27 +173,18 @@ impl JavaPlugin for AntPlugin {
192173
fn build(&self, project_root_path: &Path) -> Result<CompileResult, JavaError> {
193174
log::info!("Building project at {}", project_root_path.display());
194175

195-
let stdout_path = project_root_path.join("build_log.txt");
196-
let mut stdout = File::create(&stdout_path)
197-
.map_err(|e| JavaError::FileCreate(stdout_path.clone(), e))?;
198-
let stderr_path = project_root_path.join("build_errors.txt");
199-
let mut stderr = File::create(&stderr_path)
200-
.map_err(|e| JavaError::FileCreate(stderr_path.clone(), e))?;
201-
202176
// TODO: don't require ant in path?
203177
let ant_exec = self.get_ant_executable();
204-
let mut command = TmcCommand::new(ant_exec);
178+
let mut command = TmcCommand::new(ant_exec.to_string());
205179
command.arg("compile-test").current_dir(project_root_path);
206180
let output = command.output()?;
207181

208182
log::debug!("stdout: {}", String::from_utf8_lossy(&output.stdout));
209183
log::debug!("stderr: {}", String::from_utf8_lossy(&output.stderr));
210-
stdout
211-
.write_all(&output.stdout)
212-
.map_err(|e| JavaError::FileWrite(stdout_path, e))?;
213-
stderr
214-
.write_all(&output.stderr)
215-
.map_err(|e| JavaError::FileWrite(stderr_path, e))?;
184+
let stdout_path = project_root_path.join("build_log.txt");
185+
let stderr_path = project_root_path.join("build_errors.txt");
186+
file_util::write_to_file(&mut output.stdout.as_slice(), stdout_path)?;
187+
file_util::write_to_file(&mut output.stderr.as_slice(), stderr_path)?;
216188

217189
Ok(CompileResult {
218190
status_code: output.status,
@@ -270,7 +242,7 @@ impl JavaPlugin for AntPlugin {
270242
}
271243

272244
log::debug!("java args {} in {}", arguments.join(" "), path.display());
273-
let mut command = TmcCommand::new("java");
245+
let mut command = TmcCommand::new("java".to_string());
274246
command.current_dir(path).args(arguments);
275247
let output = command.output()?;
276248

@@ -286,6 +258,7 @@ impl JavaPlugin for AntPlugin {
286258
#[cfg(not(target_os = "macos"))] // ant is not installed on github's macos-latest image
287259
mod test {
288260
use super::*;
261+
use std::fs::{self, File};
289262
use tempfile::{tempdir, TempDir};
290263
use tmc_langs_framework::domain::Strategy;
291264
use tmc_langs_framework::zip::ZipArchive;

plugins/java/src/error.rs

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
33
use std::io;
44
use std::path::PathBuf;
5-
use std::process::ExitStatus;
65
use thiserror::Error;
7-
use tmc_langs_framework::TmcError;
6+
use tmc_langs_framework::{
7+
error::{CommandError, FileIo},
8+
TmcError,
9+
};
810

911
#[derive(Error, Debug)]
1012
pub enum JavaError {
@@ -14,43 +16,23 @@ pub enum JavaError {
1416
NoMvnClassPath,
1517
#[error("{0} did not contain a valid exercise")]
1618
InvalidExercise(PathBuf),
17-
#[error("Failed to run {0}")]
18-
FailedToRun(String, #[source] std::io::Error),
19-
#[error(
20-
r"Command '{0}' exited with a exit status {1}
21-
#### STDOUT ####
22-
{2}
23-
#### STDERR ####
24-
{3}"
25-
)]
26-
FailedCommand(String, ExitStatus, String, String),
2719
#[error("Failed to write temporary .jar file {0}")]
2820
JarWrite(PathBuf, #[source] io::Error),
29-
#[error("Failed to create file at {0}")]
30-
FileCreate(PathBuf, #[source] io::Error),
31-
#[error("Failed to write to file at {0}")]
32-
FileWrite(PathBuf, #[source] io::Error),
33-
#[error("Failed to read to file at {0}")]
34-
FileRead(PathBuf, #[source] io::Error),
35-
#[error("Failed to remove file at {0}")]
36-
FileRemove(PathBuf, #[source] io::Error),
37-
#[error("Failed to create directory at {0}")]
38-
DirCreate(PathBuf, #[source] io::Error),
3921
#[error("Failed to create temporary directory at")]
4022
TempDir(#[source] io::Error),
4123
#[error("Failed to find home directory")]
4224
HomeDir,
43-
#[error("Failed to copy file from {0} to {1}")]
44-
FileCopy(PathBuf, PathBuf, #[source] std::io::Error),
4525
#[error("Failed to find cache directory")]
4626
CacheDir,
4727
#[error("Failed to compile")]
48-
Compilation(Vec<u8>),
28+
Compilation { stdout: String, stderr: String },
4929

5030
#[error(transparent)]
5131
Json(#[from] serde_json::Error),
52-
#[error(transparent)]
53-
Inner(#[from] TmcError),
32+
#[error("Failed to run command")]
33+
Command(#[from] CommandError),
34+
#[error("Error")]
35+
Tmc(#[from] TmcError),
5436
}
5537

5638
impl From<JavaError> for TmcError {
@@ -59,6 +41,12 @@ impl From<JavaError> for TmcError {
5941
}
6042
}
6143

44+
impl From<FileIo> for JavaError {
45+
fn from(err: FileIo) -> JavaError {
46+
JavaError::Tmc(TmcError::FileIo(err))
47+
}
48+
}
49+
6250
impl<T> Into<Result<T, TmcError>> for JavaError {
6351
fn into(self) -> Result<T, TmcError> {
6452
Err(TmcError::Plugin(Box::new(self)))

0 commit comments

Comments
 (0)