On this page:
check:
pre:
post:
submission-eval
user-data
user-substs
pairs-or-singles-with-warning
teams-in-file
add-header-line!
procedure/  arity?
!defined
!bound
!syntax
!procedure
!procedure*
!integer
!integer*
!boolean
!boolean*
!test
!test/  exn
!eval
!all-covered

6.3 Checker🔗ℹ

The handin-server/checker module provides a higher-level of utilities, helpful in implementing ‘checker’ functions that are intended for a more automated system. This module is a language module—a typical checker that uses it looks like this:
(module checker handin-server/checker
  (check: :language         '(special intermediate)
          :allowed-requires '(htdp/isl/runtime
                              racket/runtime-config)
          :users            pairs-or-singles-with-warning
          :coverage?        #t
    (!procedure Fahrenheit->Celsius 1)
    (!test (Fahrenheit->Celsius  32)   0)
    (!test (Fahrenheit->Celsius 212) 100)
    (!test (Fahrenheit->Celsius  -4) -20)
    ...))

syntax

(check: keys-n-vals body ...)

 
keys-n-vals = 
  | :key val keys-n-vals
Constructs (and provides) an appropriate checker function, using keywords to customize features that you want it to have. The body of the checker (following the keywords) can contain arbitrary code, using utility functions from handin-server/utils, as well as additional ones that are defined below. Submission files are arriving to the handin server in binary form (in the GRacket format that is used to store text and other objects like images), and a number of these options involve genrating a textual version of this file. The purpose of these options is to have these text files integrate easily into a course framework for grading, based on these text files.

Keywords for configuring check::

  • :usersspecification of users that are acceptable for submission. Can be either a list of user lists, each representing a known team, or procedure which will accept a list of users and throw an exception if they are unacceptable. The default is to accept only single-user submissions. The pairs-or-singles-with-warning procedure is a useful value for pair submission where the pairs are unknown.

  • :eval?whether submissions should be evaluated. Defaults to #t. Note that if it is specified as #f, then the checker body will not be able to run any tests on the code, unless it contains code that performs some evaluation (e.g., using the facilities of handin-server/utils).

  • :languagethe language that is used for evaluating submissions. If the value is of the form (list 'module spec), then make-module-evaluator is used with spec as its #:language argument to generate an evaluator. Otherwise, the value is passed as the language argument to make-evaluator. There is no default for this, so it must be set or an error is raised. (See call-with-evaluator/submission for further details.)

  • :requirespaths for additional libraries to require for evaluating the submission, same as the requires argument for make-evaluator (see handin-server/sandbox). This defaults to null—no teachpacks. Note: if a module language is used (See call-with-evaluator/submission for further details), it is passed as the allow-read argument.

  • :teachpacksan alternative name for :requires, kept for legacy checkers.

  • :allowed-requiresmodule paths for to be allowed in require forms that appear in a module’s source or during macro expansion, or #f (the default) to disable additional require checking. Limiting require should not be necessary in principle, because unsafe operations are already limited by a code inspector, but constraining the libraries that can possibly be reference provides an extra layer of security. The modules needed by a given language depend on the language’s implementation; for example, the expansion of a racket/base module will introduce a (require racket/runtime-config).

  • :create-text?if true, then a textual version of the submission is saved as "text.rkt" in a "grading" subdirectory (or any suffix that is specified by :output below, for example "hw.java" is converted into a textual "grading/text.java"). This is intended for printouts and grading, and is in a subdirectory so students will not see it on the status web server. Defaults to #t.

  • :textualize?if true, then all submissions are converted to text, trying to convert objects like images and comment boxes to some form of text. Defaults to #f, meaning that an exception is raised for submissions that are not all text.
    (Effective only when saving a textual version of the submission files: when :create-text? is on.)

    This flag is effective only when saving a textual version of the submission files — when :create-text? is on. The possible configurations are:
    • :create-text? is on and :textualize? is off (the default) — in this case a text version of submissions is created, and submissions must contain only plain text. The text file has the same semantics of the submission and can be used to run student code.

    • :create-text? is off — allowing submissions that contain non-textual objects, but no text file is created so grading and testing must be done using DrRacket (because the saved submission is always in binary format).

    • Both flags are on — allowing submission with non-textual objects and generating text files, but these files will not be usable as code since objects like images cannot be represented in plain text.

  • :untabify?if true, then tabs are converted to spaces, assuming a standard tab width of 8 places. This is needed for a correct computation of line lengths, but note that DrRacket does not insert tabs in Scheme mode. Defaults to #t.
    (Effective only when saving a textual version of the submission files: when :create-text? is on.)

  • :maxwidtha number that specifies maximum line lengths for submissions (a helpful feature for reading student code). Defaults to 79. This feature can be disabled if set to #f.
    (Effective only when saving a textual version of the submission files: when :create-text? is on.)

  • :outputthe name of the original handin file (unrelated to the text-converted files). Defaults to "hw.rkt". (The suffix changes the defaults of :markup-prefix and :prefix-re.) Can be #f for removing the original file after processing. The file is always stored in GRacket’s binary format.

  • :multi-fileby default, this is set to #f, which means that only DrRacket is used to send submissions as usual. See Multiple-File Submissions for setting up multi-file submissions.

  • :names-checkerused for multi-file submissions; see Multiple-File Submissions for details.

  • :markup-prefixused as the prefix for :student-lines and :extra-lines below. The default is ";;> " or "//> ", depending on the suffix of :output above. Note: if you change this, make sure to change :prefix-re too.
    (Effective only when saving a textual version of the submission files: when :create-text? is on.)

  • :prefix-reused to identify lines with markup (";>" or "//>" etc), so students cannot fool the system by writing marked-up code. The default is ";>" or "//>", depending on the suffix of :output above.
    (Effective only when saving a textual version of the submission files: when :create-text? is on.)

  • :student-linewhen a submission is converted to text, it begins with lines describing the students that have submitted it; this is used to specify the format of these lines. It is a string with holes that user-substs fills out. The default is "Student: {username} ({Full Name} <{Email}>)", which requires "Full Name" and "Email" entries in the server’s extra-fields configuration. These lines are prefixed with ";;> " or the prefix specified by :makup-prefix above.
    (Effective only when saving a textual version of the submission files: when :create-text? is on.)

  • :extra-linesa list of lines to add after the student lines, all with a ";;> " or :markup-prefix too. Defaults to a single line: "Maximum points for this assignment: <+100>". (Can use "{submission}" for the submission directory.) See also add-header-line!.
    (Effective only when saving a textual version of the submission files: when :create-text? is on.)

  • :user-error-messagea string that is used to report an error that occurred during evaluation of the submitted code (not during additional tests). It can be a plain string which will be used as the error message, or a string with single a "~a" (or "~s", "~v", "~e", or "~.a" etc) that will be used as a format string with the actual error message. The default is "Error in your code --\n~a". Useful examples of these messages:

    "There is an error in your program, hit \"Run\" to debug"

    "There is an error in your program:\n----\n~a\n----\nHit \"Run\" and debug your code."

    Alternatively, the value can be a procedure that will be invoked with the error message. The procedure can do anything it wants, and if it does not raise an exception, then the checker will proceed as usual. For example:

    (lambda (msg)
      (add-header-line! "Erroneous submission!")
      (add-header-line! (format "  --> ~a" msg))
      (message (string-append
                "You have an error in your program -- please hit"
                " \"Run\" and debug your code.\n"
                "Email the course staff if you think your code is"
                " fine.\n"
                "(The submission has been saved but marked as"
                " erroneous.)")
               '(ok))
      (message "Handin saved as erroneous." 'final))

    (Note that if you do this, then additional tests should be adjusted to not raise an exception too.)

  • :value-printerif specified, this will be used for current-value-printer.

  • :coverage?collect coverage information when evaluating the submission. This will cause an error if some input is not covered. This check happens after checker tests are run, but the information is collected and stored before, so checker tests do not change the result. Also, you can use the !all-covered procedure in the checker before other tests, if you want that feedback earlier.

Within the body of check:, users and submission will be bound to the checker arguments—a (sorted) list of usernames and the submission as a byte string. In addition to the functionality below, you can use (!eval expr) (or ((submission-eval) 'expr)) to evaluate expressions in the submitted code context, and you can use (with-submission-bindings (id ...) body ...) to evaluate the body when id’s are bound to their values from the submission code.}

syntax

(pre: body ...)

syntax

(post: body ...)

These two macros define a pre- and a post-checker. In their bodies, users and submission are bound as in check:, but there is nothing else special about these. See the description of the pre-checker and post-checker values for what can be done with these, and note that the check for valid users is always first. An example for a sophisticated post: block is below—it will first set a very long timeout for this session, then it will send an email with a submission receipt, with a CC to the TA (assuming a single TA), and pop-up a message telling the student about it:

(require net/sendmail)
(post:
  (define info
    (format "hw.rkt: ~a ~a"
            (file-size "hw.rkt")
            (file-or-directory-modify-seconds "hw.rkt")))
  (timeout-control 300)
  (log-line "Sending a receipt: ~a" info)
  (send-mail-message
   "course-staff@university.edu"
   "Submission Receipt"
   (map (lambda (user) (user-substs user "{Full Name} <{Email}>"))
        users)
   (list (user-substs (car users) "{TA Name} <{TA Email}>"))
   null
   `("Your submission was received" ,info))
  (message (string-append
            "Your submission was successfully saved."
            "  You will get an email receipt within 30 minutes;"
            " if not, please contact the course staff.")
           '(ok)))

parameter

(submission-eval)  (any/c . -> . any)

(submission-eval eval)  void?
  eval : (any/c . -> . any)
Holds an evaluation procedure for evaluating code in the submission context.

procedure

(user-data user)  (listof string?)

  user : string?
Returns a user information given a username. The returned information is a list of strings that corresponds to the configured extra-fields.

procedure

(user-substs user fmt)  string

  user : string?
  fmt : string?
Uses the mappings in user-data to substitute user information for substrings of the form “{some-field-name}” in fmt. This procedure signals an error if a field name is missing in the user data. Also, “{username}” will always be replaced by the username and “{submission}” by the current submission directory.

This is used to process the :student-line value in the checker, but it is provided for additional uses. See the above sample code for post: for using this procedure.

procedure

(pairs-or-singles-with-warning users)  any

  users : (listof string?)
Intended for use as the :users entry in a checker. It will do nothing if there are two users, and throw an error if there are more. If there is a single user, then the user will be asked to verify a single submission. If the student cancels, then an exception is raised so the submission directory is retracted. If the student approves this, the question is not repeated (this is marked by creating a directory with a known name). This is useful for cases where you want to allow free pair submissions—students will often try to submit their work alone, and later on re-submit with a partner.

procedure

(teams-in-file team-file)  ((listof string?) . -> . void?)

  team-file : path-string?
Returns a procedure that can be used for the :users entry in a checker. The team file (relative from the server’s main directory) is expected to have user entries—a sequence of s-expressions, each one a string or a list of strings. The resulting procedure will allow submission only by teams that are specified in this file. Furthermore, if this file is modified, the new contents will be used immediately, so there is no need to restart the server of you want to change student teams. (But remember that if you change ("foo" "bar") to ("foo" "baz"), and there is already a "bar+foo" submission directory, then the system will not allow “foo” to submit with “bar”.)

procedure

(add-header-line! line)  void?

  line : string?
During the checker operation, can be used to add header lines to the text version of the submitted file (in addition to the :extra-lines setting). It will not have an effect if :create-text? is false.

procedure

(procedure/arity? proc arity)  boolean?

  proc : procedure?
  arity : number?
Returns #t if proc is a procedure that accepts arity arguments.

syntax

(!defined id ...)

Checks that the given identifiers are defined in the (evaluated) submission, and throws an error otherwise. The identifiers can be bound as either a plain value or as a syntax.

syntax

(!bound id ...)

Checks that the given identifiers are defined in the (evaluated) submission as a plain value. Throws an error if not, or if an identifier is bound to a syntax.

syntax

(!syntax id arity)

Checks that id is defined, and is bound as a macro.

syntax

(!procedure id arity)

Checks that id is defined, and is bound to a procedure.

syntax

(!procedure* expr arity)

Similar to !procedure but omits the defined check, making it usable with any expression, which is then evaluated in the submission context.

syntax

(!integer id)

syntax

(!integer* expr)

Similar to !procedure and !procedure* for integers.

syntax

(!boolean id)

syntax

(!boolean* expr)

Similar to !procedure and !procedure* for booleans.

syntax

(!test expr)

(!test expr result)
(!test expr result equal?)

syntax

(!test/exn expr)

The first !test form checks that the given expression evaluates to a non-#f value in the submission context, throwing an error otherwise. The second form compares the result of evaluation, requiring it to be equal to result. The third allows specifying an equality procedure. The !test/exn form checks that the given expression throws an exn:fail? error, throwing an error otherwise.

For the latter two !test forms, note that the result and equal? forms are not evaluated in the submission context.

syntax

(!eval expr)

Evaluate an arbitrary expression in the submission context. This is a simple shorthand for ((submission-eval) `expr).

procedure

(!all-covered)  void?

(!all-covered proc)  void?
  proc : (string? . -> . any)
When coverage information is enabled (see :coverage? above), checks the collected coverage information and throws an error with source information if some code is left uncovered. If proc is provided, it is applied to a string argument that describes the location of the uncovered expression ("<line>:<col>", "#<char-pos>", or "(unknown position)") instead of throwing an error. The collected information includes only execution coverage by submission code, excluding additional checker tests. You do not have to call this explicitly—it is called at the end of the process automatically when :coverage? is enabled. It is made available so you can call it earlier (e.g., before testing) to show clients a coverage error first, or if you want to avoid an error. For example, you can do this:

(!all-covered
 (lambda (where)
   (case (message (string-append
                   "Incomplete coverage at "where", do you want"
                   " to save this submission with 10% penalty?")
                  '(yes-no))
     [(yes) (add-header-line! "No full coverage <*90%>")
            (message "Handin saved with penalty.")]
     [else (error "aborting submission")])))