Class: Course::Assessment::ProgrammingPackage

Inherits:
Object
  • Object
show all
Defined in:
lib/autoload/course/assessment/programming_package.rb

Overview

Represents a programming package, containing the tests and submitted code for a given question.

A package has these files at the very minimum:

  • +Makefile+: the makefile for building and executing the package. There are at least three targets:
    • +prepare+: for initialising the environment. This can be used to set up libraries or other modifications to the package.
    • +compile+: for building the package. For scripting languages, this is a no-op, but must still be defined
    • +test+: for testing the package. After completing the task, +tests.junit.xml+ must be found in the root directory of the package (beside the +tests/+ directory)
  • +submission/+: where the template code/students' code will be placed. When this package is uploaded by the instructor as part of a question, this directory will contain the templates that will be used when a student first attempts the question. When this package is generated as part of auto grading, then this contains all the student's submitted code.
  • +tests/+: where the tests will be placed. How this set of tests should be run is immaterial, so long it is run when +make test+ is executed by the evaluator.

It can also contain an optional 'solution' folder:

+solution/+: where syntax correct code is placed. When the package is uploaded by the instructor as part of a question, the contents of this directory will replace the contents of the 'submission' folder. This allows infinite loops or incorrect syntax to be used as templates for the students to fix. It also allows solutions to be kept in the same place as the tests. When this package is generated as part of auto grading, this folder is removed to prevent student code from accessing it.

Call #close when changes have been made to persist the changes to disk. Duplicate the file before modifying if you do not want to modify the original file -- changes are made in-place.

Constant Summary collapse

META_PATH =

The path to the .meta file.

Pathname.new('.meta').freeze
MAKEFILE_PATH =

The path to the Makefile.

Pathname.new('Makefile').freeze
SUBMISSION_PATH =

The path to the submission.

Pathname.new('submission').freeze
SOLUTION_PATH =

The path to the solution.

Pathname.new('solution').freeze
TESTS_PATH =

The path to the tests.

Pathname.new('tests').freeze

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ ProgrammingPackage #initialize(stream) ⇒ ProgrammingPackage

Creates a new programming package instance.


55
56
57
58
59
60
61
62
63
64
# File 'lib/autoload/course/assessment/programming_package.rb', line 55

def initialize(path_or_stream)
  case path_or_stream
  when String, Pathname
    @path = path_or_stream
  when IO
    @stream = path_or_stream
  else
    raise ArgumentError, 'Invalid path or stream object'
  end
end

Instance Method Details

#closeObject

Closes the package.


81
82
83
84
85
# File 'lib/autoload/course/assessment/programming_package.rb', line 81

def close
  ensure_file_open!
  @file.close
  @file = nil
end

#meta_fileString

Gets the .meta file.


108
109
110
111
112
# File 'lib/autoload/course/assessment/programming_package.rb', line 108

def meta_file
  get_file(META_PATH)
rescue
  nil
end

#pathString?

Gets the file path to the provided package.


70
71
72
73
74
75
76
77
78
# File 'lib/autoload/course/assessment/programming_package.rb', line 70

def path
  if @file
    @file.name
  elsif @path
    @path.to_s
  elsif @stream.is_a?(File)
    @stream.path
  end
end

#remove_solution_filesObject

Remove the contents of the solution folder so students can't do sneaky things like generating a report from the solution folder.


141
142
143
# File 'lib/autoload/course/assessment/programming_package.rb', line 141

def remove_solution_files
  remove_folder_files(SOLUTION_PATH)
end

#replace_submission_with_solutionObject

If a solution directory exists, replace the contents of the submission directory with the contents of the solution directory. Allows syntax incorrect templates since the import uses other files.


156
157
158
159
160
161
162
# File 'lib/autoload/course/assessment/programming_package.rb', line 156

def replace_submission_with_solution
  # Return if there are no files in the solution directory
  files = solution_files
  return if files.empty?

  self.submission_files = files
end

#saveBoolean

Commits the package changes to disk


90
91
92
93
94
# File 'lib/autoload/course/assessment/programming_package.rb', line 90

def save
  ensure_file_open!
  @file.commit
  true
end

#solution_filesHash<Pathname, String>

Gets the contents of all solution files.


149
150
151
# File 'lib/autoload/course/assessment/programming_package.rb', line 149

def solution_files
  get_folder_files(SOLUTION_PATH)
end

#submission_filesHash<Pathname, String>

Gets the contents of all submission files.


118
119
120
# File 'lib/autoload/course/assessment/programming_package.rb', line 118

def submission_files
  get_folder_files(SUBMISSION_PATH)
end

#submission_files=(files) ⇒ Object

Replaces the contents of all submission files.


126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/autoload/course/assessment/programming_package.rb', line 126

def submission_files=(files)
  ensure_file_open!
  remove_folder_files(SUBMISSION_PATH)

  files.each do |path, file|
    path = Pathname.new(path) unless path.is_a?(Pathname)
    raise ArgumentError, 'Paths must be relative' unless path.relative?
    @file.get_output_stream(SUBMISSION_PATH.join(path)) do |stream|
      stream.write(file)
    end
  end
end

#unzip_file(destination) ⇒ Object

Unzips the contents of the file to the destination folder.


167
168
169
170
171
172
173
174
# File 'lib/autoload/course/assessment/programming_package.rb', line 167

def unzip_file(destination)
  ensure_file_open!
  @file.each do |entry|
    entry_path = File.join(destination, entry.name)
    FileUtils.mkdir_p(File.dirname(entry_path))
    @file.extract(entry, entry_path) unless File.exist?(entry_path)
  end
end

#valid?Boolean

Checks if the given programming package is valid.


99
100
101
102
103
# File 'lib/autoload/course/assessment/programming_package.rb', line 99

def valid?
  ensure_file_open!

  ['Makefile', 'submission/', 'tests/'].all? { |entry| @file.find_entry(entry).present? }
end