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

Add Rust Autotest (DB Entries) #6906

Closed
Closed
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 Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [unreleased]
- Allow deletion of assignments with no groups (#6880)
- Added a rust auto-test target (#6906)
- Add new routes for `update`, `show`, and `index` actions of the Sections API Controller (#6955)
- Enable the deletion of Grade Entry Forms that have no grades (#6915)
- Fixed login_spec.rb flaky test on GitHub Actions run (#6966)
Expand Down
2 changes: 2 additions & 0 deletions db/data/autotest_files/rust/script_files/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod queue;
mod tests;
48 changes: 48 additions & 0 deletions db/data/autotest_files/rust/script_files/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#[cfg(test)]
mod tests {
use crate::queue::MyQueue;

#[test]
pub fn test_dequeue_empty() {
let mut queue: MyQueue<i32, 4> = MyQueue::new();

assert!(queue.dequeue().is_none());
}

#[test]
pub fn test_enqueue_size() {
let mut queue: MyQueue<i32, 4> = MyQueue::new();

assert!(queue.enqueue(4).is_some());
assert_eq!(queue.size(), 1);
}

#[test]
pub fn test_enqueue_dequeue_value() {
let mut queue: MyQueue<i32, 4> = MyQueue::new();

assert!(queue.enqueue(4).is_some());
assert_eq!(queue.dequeue(), Some(4));
}

#[test]
pub fn test_enqueue_dequeue_many_values() {
let mut queue: MyQueue<i32, 4> = MyQueue::new();

assert!(queue.enqueue(4).is_some());
assert!(queue.enqueue(5).is_some());
assert_eq!(queue.dequeue(), Some(4));
assert_eq!(queue.dequeue(), Some(5));
}

#[test]
pub fn test_queue_full() {
let mut queue: MyQueue<i32, 2> = MyQueue::new();

assert!(queue.enqueue(4).is_some());
assert!(queue.enqueue(5).is_some());
assert!(queue.enqueue(6).is_none());

assert_eq!(queue.size(), 2);
}
}
19 changes: 19 additions & 0 deletions db/data/autotest_files/rust/specs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"testers": [
{
"tester_type": "rust",
"test_data": [
{
"category": [
"instructor"
],
"timeout": 30,
"extra_info": {
"criterion": "criterion",
"name": "Rust Test Group"
}
}
]
}
]
}
8 changes: 8 additions & 0 deletions db/data/autotest_files/rust/student_files/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "test-time"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
1 change: 1 addition & 0 deletions db/data/autotest_files/rust/student_files/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod queue;
49 changes: 49 additions & 0 deletions db/data/autotest_files/rust/student_files/src/queue.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
pub struct MyQueue<T, const N: usize> {
count: usize,
start: usize,
buffer: [Option<T>; N] // We could use MaybeUninit here instead.
}

impl<T, const N: usize> MyQueue<T, N> {
pub fn new() -> MyQueue<T, N> {
// Fill array with None (we don't have Copy + Clone so buffer: [None; N] is no good!)
MyQueue {
count: 0,
start: 0,
buffer: std::array::from_fn(|_| None)
}
}

pub fn enqueue(&mut self, value: T) -> Option<()> {
if self.count >= N {
return None
}

let end = (self.start + self.count) % N;

self.buffer[end] = Some(value);

self.count += 1;

Some(())
}

pub fn dequeue(&mut self) -> Option<T> {
if self.count <= 0 {
return None
}

let value = self.buffer[self.start].take();

self.count -= 1;

// Intentional error for tests to catch.
// self.start += 1;

value
}

pub fn size(&self) -> usize {
self.count
}
}
34 changes: 24 additions & 10 deletions lib/tasks/autotest.rake
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ class AutotestSetup
def initialize(root_dir)
# setup instance variables (mostly paths to directories)
@assg_short_id = "autotest_#{File.basename(root_dir)}"
script_dir = File.join(root_dir, 'script_files')
@test_scripts = Dir.glob(File.join(script_dir, '*'))
@script_dir = File.join(root_dir, 'script_files')
@test_scripts = Dir.glob(File.join(@script_dir, '*'))
@specs_file = File.join(root_dir, 'specs.json')
@specs_data = JSON.parse(File.read(@specs_file))

Expand Down Expand Up @@ -102,8 +102,10 @@ class AutotestSetup
test_file_destination = @assignment.autotest_files_dir
FileUtils.makedirs test_file_destination

return unless File.directory?(@script_dir)

# copy test scripts and specs files into the destination directory
FileUtils.cp @test_scripts, test_file_destination
FileUtils.copy_entry @script_dir, test_file_destination
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will copy folders too. Rust expects the default library file to be called src/lib.rs (within the src folder). This is configurable in Cargo.toml, but I thought I would provide an example that uses the default project structure for clarity 👍

end

def create_marking_scheme
Expand All @@ -127,6 +129,24 @@ class AutotestSetup
)
end

def create_submission_files(transaction, glob)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, this recursive definition is designed to support the src folder in the rust submission.

glob.each do |file_path|
if File.directory?(file_path)
transaction.add_path(file_path)

next_glob = Dir.glob(File.join(file_path, '*'))

create_submission_files(transaction, next_glob)
else
File.open(file_path, 'r') do |file|
file_rel_path = Pathname.new(file_path).relative_path_from Pathname.new(@student_dir)
repo_path = File.join(@assignment.repository_folder, file_rel_path)
transaction.add(repo_path, file.read, '')
end
end
end
end

def create_submission
return if @assignment.groupings.exists?

Expand All @@ -136,13 +156,7 @@ class AutotestSetup
grouping = Grouping.find_by(group_id: group, assessment_id: @assignment.id)
grouping.access_repo do |repo|
transaction = repo.get_transaction(student.user_name)
@student_files.each do |file_path|
File.open(file_path, 'r') do |file|
file_rel_path = Pathname.new(file_path).relative_path_from Pathname.new(@student_dir)
repo_path = File.join(@assignment.repository_folder, file_rel_path)
transaction.add(repo_path, file.read, '')
end
end
create_submission_files(transaction, @student_files)
repo.commit(transaction)
end
# create new submission for each grouping
Expand Down