Skip to content

Commit c42e25f

Browse files
authored
Merge pull request #86 from rage/scanfix
Fix some exercise points being incorrectly given
2 parents 1ee009f + a44eee8 commit c42e25f

File tree

10 files changed

+191
-22
lines changed

10 files changed

+191
-22
lines changed
412 KB
Binary file not shown.
-360 KB
Binary file not shown.

plugins/csharp/src/cs_test_result.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Contains the CSTestResult type that models the C# test runner result.
22
33
use serde::Deserialize;
4+
use std::collections::HashSet;
45
use tmc_langs_framework::domain::TestResult;
56

67
/// Test result from the C# test runner.
@@ -14,14 +15,15 @@ pub struct CSTestResult {
1415
pub error_stack_trace: Vec<String>,
1516
}
1617

17-
impl From<CSTestResult> for TestResult {
18-
fn from(test_result: CSTestResult) -> Self {
18+
impl CSTestResult {
19+
pub fn into_test_result(mut self, failed_points: &HashSet<String>) -> TestResult {
20+
self.points.retain(|point| !failed_points.contains(point));
1921
TestResult {
20-
name: test_result.name,
21-
successful: test_result.passed,
22-
message: test_result.message,
23-
exception: test_result.error_stack_trace,
24-
points: test_result.points,
22+
name: self.name,
23+
successful: self.passed,
24+
message: self.message,
25+
exception: self.error_stack_trace,
26+
points: self.points,
2527
}
2628
}
2729
}

plugins/csharp/src/plugin.rs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use crate::policy::CSharpStudentFilePolicy;
44
use crate::{cs_test_result::CSTestResult, CSharpError};
5-
use std::collections::HashMap;
5+
use std::collections::{HashMap, HashSet};
66
use std::env;
77
use std::ffi::{OsStr, OsString};
88
use std::io::{BufReader, Cursor, Read, Seek};
@@ -24,7 +24,7 @@ use tmc_langs_framework::{
2424
};
2525
use walkdir::WalkDir;
2626

27-
const TMC_CSHARP_RUNNER: &[u8] = include_bytes!("../deps/tmc-csharp-runner-1.1.zip");
27+
const TMC_CSHARP_RUNNER: &[u8] = include_bytes!("../deps/tmc-csharp-runner-1.1.1.zip");
2828

2929
#[derive(Default)]
3030
pub struct CSharpPlugin {}
@@ -142,15 +142,19 @@ impl CSharpPlugin {
142142
.map_err(|e| CSharpError::ParseTestResults(test_results_path.to_path_buf(), e))?;
143143

144144
let mut status = RunStatus::Passed;
145+
let mut failed_points = HashSet::new();
145146
for test_result in &test_results {
146147
if !test_result.passed {
147-
log::info!("C# tests failed");
148148
status = RunStatus::TestsFailed;
149-
break;
149+
failed_points.extend(test_result.points.iter().cloned());
150150
}
151151
}
152+
152153
// convert the parsed C# test results into TMC test results
153-
let test_results = test_results.into_iter().map(|t| t.into()).collect();
154+
let test_results = test_results
155+
.into_iter()
156+
.map(|t| t.into_test_result(&failed_points))
157+
.collect();
154158
Ok(RunResult {
155159
status,
156160
test_results,
@@ -697,4 +701,21 @@ mod test {
697701
let res = CSharpPlugin::points_parser("@ pOiNtS ( \" 1 \" ) ").unwrap();
698702
assert_eq!(res.1, "1");
699703
}
704+
705+
#[test]
706+
#[ignore = "requires newer version of C# runner that always includes all points in the tests"]
707+
fn doesnt_give_points_unless_all_relevant_exercises_pass() {
708+
init();
709+
710+
let temp = dir_to_temp("tests/data/partially-passing");
711+
let plugin = CSharpPlugin::new();
712+
let results = plugin.run_tests(temp.path(), &mut vec![]).unwrap();
713+
assert_eq!(results.status, RunStatus::TestsFailed);
714+
let mut got_point = false;
715+
for test in results.test_results {
716+
got_point = got_point || test.points.contains(&"1.2".to_string());
717+
assert!(!test.points.contains(&"1".to_string()));
718+
assert!(!test.points.contains(&"1.1".to_string()));
719+
}
720+
}
700721
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
3+
namespace TestProject
4+
{
5+
public class Program
6+
{
7+
public static bool ReturnTrue => true;
8+
9+
public static bool ReturnNotInput(bool input) => !input;
10+
public static string ReturnInputString(string input) => input;
11+
12+
public static void Main(string[] args)
13+
{
14+
//BEGIN SOLUTION
15+
Console.WriteLine("Hello Home");
16+
//END SOLUTION
17+
//STUB: Console.WriteLine("Stub");
18+
}
19+
}
20+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net5.0</TargetFramework>
6+
</PropertyGroup>
7+
8+
</Project>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
using Xunit;
3+
using TestProject;
4+
using TestMyCode.CSharp.API.Attributes;
5+
6+
namespace TestProjectTests
7+
{
8+
[Points("1")]
9+
public class ProgramTest
10+
{
11+
[Fact]
12+
[Points("1.1")]
13+
public void TestReturnsTrue()
14+
{
15+
Assert.True(false);
16+
}
17+
18+
[Fact]
19+
[Points("1.1")]
20+
public void ReturnsNotInput()
21+
{
22+
Assert.True(true);
23+
}
24+
25+
[Fact]
26+
[Points("1.2")]
27+
public void ReturnsString()
28+
{
29+
Assert.True(true);
30+
}
31+
32+
[Fact]
33+
public void TestForClassPoint()
34+
{
35+
Assert.True(true);
36+
}
37+
38+
public void NotAPointTest()
39+
{
40+
Assert.True(false);
41+
}
42+
}
43+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net5.0</TargetFramework>
5+
<IsPackable>false</IsPackable>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
10+
<PackageReference Include="TestMyCode.CSharp.API" Version="1.1" />
11+
<PackageReference Include="xunit" Version="2.4.1" />
12+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<ProjectReference Include="../../src/TestProject/TestProject.csproj" />
17+
</ItemGroup>
18+
</Project>

plugins/python3/src/plugin.rs

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::error::PythonError;
44
use crate::policy::Python3StudentFilePolicy;
55
use crate::python_test_result::PythonTestResult;
66
use lazy_static::lazy_static;
7-
use std::collections::HashMap;
7+
use std::collections::{HashMap, HashSet};
88
use std::env;
99
use std::io::BufReader;
1010
use std::path::{Path, PathBuf};
@@ -176,17 +176,20 @@ impl Python3Plugin {
176176
let test_results: Vec<PythonTestResult> =
177177
serde_json::from_reader(BufReader::new(results_file))
178178
.map_err(|e| PythonError::Deserialize(test_results_json.to_path_buf(), e))?;
179-
let test_results: Vec<TestResult> = test_results
180-
.into_iter()
181-
.map(PythonTestResult::into_test_result)
182-
.collect();
183179

184180
let mut status = RunStatus::Passed;
181+
let mut failed_points = HashSet::new();
185182
for result in &test_results {
186-
if !result.successful {
183+
if !result.passed {
187184
status = RunStatus::TestsFailed;
185+
failed_points.extend(result.points.iter().cloned());
188186
}
189187
}
188+
189+
let test_results: Vec<TestResult> = test_results
190+
.into_iter()
191+
.map(|r| r.into_test_result(&failed_points))
192+
.collect();
190193
Ok(RunResult::new(status, test_results, logs))
191194
}
192195
}
@@ -253,7 +256,21 @@ impl LanguagePlugin for Python3Plugin {
253256
if test_results_json.exists() {
254257
file_util::remove_file(&test_results_json)?;
255258
}
256-
Ok(parse_res?)
259+
260+
let mut run_result = parse_res?;
261+
262+
// remove points associated with any failed tests
263+
let mut failed_points = HashSet::new();
264+
for test_result in &run_result.test_results {
265+
if !test_result.successful {
266+
failed_points.extend(test_result.points.iter().cloned());
267+
}
268+
}
269+
for test_result in &mut run_result.test_results {
270+
test_result.points.retain(|p| !failed_points.contains(p));
271+
}
272+
273+
Ok(run_result)
257274
}
258275
Err(PythonError::Tmc(TmcError::Command(CommandError::TimeOut {
259276
stdout,
@@ -531,10 +548,10 @@ class TestFailing(unittest.TestCase):
531548

532549
let plugin = Python3Plugin::new();
533550
let run_result = plugin.run_tests(temp_dir.path(), &mut vec![]).unwrap();
551+
log::debug!("{:#?}", run_result);
534552
assert_eq!(run_result.status, RunStatus::TestsFailed);
535553
assert_eq!(run_result.test_results[0].name, "TestFailing: test_func");
536554
assert!(!run_result.test_results[0].successful);
537-
assert!(run_result.test_results[0].points.contains(&"1.1".into()));
538555
assert!(run_result.test_results[0].message.starts_with("'a' != 'b'"));
539556
assert!(!run_result.test_results[0].exception.is_empty());
540557
assert_eq!(run_result.test_results.len(), 1);
@@ -562,10 +579,10 @@ class TestErroring(unittest.TestCase):
562579

563580
let plugin = Python3Plugin::new();
564581
let run_result = plugin.run_tests(temp_dir.path(), &mut vec![]).unwrap();
582+
log::debug!("{:#?}", run_result);
565583
assert_eq!(run_result.status, RunStatus::TestsFailed);
566584
assert_eq!(run_result.test_results[0].name, "TestErroring: test_func");
567585
assert!(!run_result.test_results[0].successful);
568-
assert!(run_result.test_results[0].points.contains(&"1.1".into()));
569586
assert_eq!(
570587
run_result.test_results[0].message,
571588
"name 'doSomethingIllegal' is not defined"
@@ -708,4 +725,42 @@ class TestErroring(unittest.TestCase):
708725
let res = Python3Plugin::find_project_dir_in_zip(&mut zip);
709726
assert!(res.is_err());
710727
}
728+
729+
#[test]
730+
fn doesnt_give_points_unless_all_relevant_exercises_pass() {
731+
init();
732+
733+
let temp_dir = temp_with_tmc();
734+
file_to(&temp_dir, "test/__init__.py", "");
735+
file_to(
736+
&temp_dir,
737+
"test/test_file.py",
738+
r#"
739+
import unittest
740+
from tmc import points
741+
742+
@points('1')
743+
class TestClass(unittest.TestCase):
744+
@points('1.1', '1.2')
745+
def test_func1(self):
746+
self.assertTrue(False)
747+
748+
@points('1.1', '1.3')
749+
def test_func2(self):
750+
self.assertTrue(True)
751+
"#,
752+
);
753+
754+
let plugin = Python3Plugin::new();
755+
let results = plugin.run_tests(temp_dir.path(), &mut vec![]).unwrap();
756+
assert_eq!(results.status, RunStatus::TestsFailed);
757+
let mut got_point = false;
758+
for test in results.test_results {
759+
got_point = got_point || test.points.contains(&"1.3".to_string());
760+
assert!(!test.points.contains(&"1".to_string()));
761+
assert!(!test.points.contains(&"1.1".to_string()));
762+
assert!(!test.points.contains(&"1.2".to_string()));
763+
}
764+
assert!(got_point);
765+
}
711766
}

plugins/python3/src/python_test_result.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use serde::{Deserialize, Serialize};
2+
use std::collections::HashSet;
23
use tmc_langs_framework::domain::TestResult;
34

45
#[derive(Debug, Deserialize, Serialize)]
@@ -12,7 +13,8 @@ pub struct PythonTestResult {
1213
}
1314

1415
impl PythonTestResult {
15-
pub fn into_test_result(self) -> TestResult {
16+
pub fn into_test_result(mut self, failed_points: &HashSet<String>) -> TestResult {
17+
self.points.retain(|point| !failed_points.contains(point));
1618
TestResult {
1719
name: parse_test_name(self.name),
1820
successful: self.passed,

0 commit comments

Comments
 (0)