* Factored out and generalized sandbox code for standalone testing
* Renamed "extra-utils.ss" to "checker.ss" svn: r5356
This commit is contained in:
parent
0a36385258
commit
c98b4a15b2
|
@ -33,7 +33,7 @@ server and each user's password.
|
||||||
|
|
||||||
|
|
||||||
Quick Start for a Test Drive:
|
Quick Start for a Test Drive:
|
||||||
============================================
|
=============================
|
||||||
|
|
||||||
1. Create a new directory.
|
1. Create a new directory.
|
||||||
|
|
||||||
|
@ -83,10 +83,8 @@ Quick Start for a Test Drive:
|
||||||
-------------------------------------------------------------------
|
-------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
Client Customization
|
Client Customization:
|
||||||
============================================
|
=====================
|
||||||
|
|
||||||
To customize the client:
|
|
||||||
|
|
||||||
1. Rename (or make a copy of) the "handin-client" collection
|
1. Rename (or make a copy of) the "handin-client" collection
|
||||||
directory. The new name should describe your class uniquely.
|
directory. The new name should describe your class uniquely.
|
||||||
|
@ -139,8 +137,8 @@ To customize the client:
|
||||||
--all-users flag.
|
--all-users flag.
|
||||||
|
|
||||||
|
|
||||||
Server Setup
|
Server Setup:
|
||||||
============================================
|
=============
|
||||||
|
|
||||||
You must prepare a special directory to host the handin server. To
|
You must prepare a special directory to host the handin server. To
|
||||||
run the server, you should either be in this directory, or you should
|
run the server, you should either be in this directory, or you should
|
||||||
|
@ -556,26 +554,102 @@ to start with a specific assignment (named ASSIGNMENT). The default
|
||||||
PORT is 7980.
|
PORT is 7980.
|
||||||
|
|
||||||
|
|
||||||
Checker Utilities
|
Checker Utilities:
|
||||||
============================================
|
==================
|
||||||
|
|
||||||
The _utils.ss_ module provides utilities helpful in implementing
|
The checker utilities are provided to make writing checker functions.
|
||||||
`checker' functions:
|
They are provided in a few layers, each layer provides new
|
||||||
|
functionality in addition to the lower one. These modules are (in
|
||||||
|
order):
|
||||||
|
|
||||||
> (get-conf key)
|
* sandbox.ss -- basic sandbox evaluation utilities, can be used
|
||||||
Returns a value from the configuration file (useful for reading
|
independently from the handin-server.
|
||||||
things like field names etc)
|
|
||||||
|
|
||||||
> (unpack-submission bytes)
|
* utils.ss -- additional utilities for dealing with handin
|
||||||
Returns two text% objects corresponding to the submitted definitions
|
submissions, as well as a few helpers for testing code.
|
||||||
and interactions windows.
|
|
||||||
|
|
||||||
> (make-evaluator language teachpack-paths program-port)
|
* checker.ss -- this layer automates the task of creating a checker
|
||||||
Returns a function of one required argument for evaluating
|
function (in "<active-assignment>/checker.ss" modules) to cope with
|
||||||
expressions in the designated language, and loading teachpacks that
|
common submission situations.
|
||||||
are specified in `teachpack-paths'. The `program-port' is an input
|
|
||||||
port that produces the content of the definitions window; use
|
The following sections describe each of these modules.
|
||||||
`(open-input-string "")' for an empty definitions window.
|
|
||||||
|
|
||||||
|
_sandbox.ss_
|
||||||
|
------------
|
||||||
|
|
||||||
|
The main function that is implemented in this module is
|
||||||
|
`make-evaluator'. Most of the functionality that is provided is used
|
||||||
|
by this function.
|
||||||
|
|
||||||
|
> mred?
|
||||||
|
A boolean that is bound to `#t' if we're currently running in MrEd,
|
||||||
|
`#f' if in plain MzScheme. The idea is that you can use this module
|
||||||
|
both from MzScheme or, if needed, from MrEd. (Higher levels
|
||||||
|
("utils.ss" and "checker.ss"), need to be used with MrEd.)
|
||||||
|
|
||||||
|
> coverage-enabled
|
||||||
|
A boolean parameter that controls whether coverage testing is
|
||||||
|
enabled in `make-evaluator'-created functions. If it set to true,
|
||||||
|
the "handin-server/private/coverage.ss" module will be used to
|
||||||
|
detect uncovered expressions. This information is collected after
|
||||||
|
the input port has been evaluated, so it is not affected by testing
|
||||||
|
code that is not part of the submission. To retrieve the collected
|
||||||
|
information, apply the evaluation function on the special
|
||||||
|
`get-uncovered-expressions' value below. The resulting value is a
|
||||||
|
list of uncovered expressions, with at most one per position+span
|
||||||
|
(which means that the contents may be unreliable, but the position
|
||||||
|
is). The default is `#f'.
|
||||||
|
|
||||||
|
> get-uncovered-expressions
|
||||||
|
A special value that, when passed to an evaluator created by
|
||||||
|
`make-evaluator', will return a list of uncovered syntax objects.
|
||||||
|
|
||||||
|
> namespace-specs
|
||||||
|
A parameter that holds a list of values that specify how to create a
|
||||||
|
namespace for evaluation in `make-evaluator'. The first item in the
|
||||||
|
list is a thunk that creates the namespace, and the rest are require
|
||||||
|
specs for modules that are to be attached to the created namespace.
|
||||||
|
The default is `make-namespace' and `(lib "posn.ss" "lang")' if
|
||||||
|
running in MzScheme, or `make-namespace-with-mred' and
|
||||||
|
`(lib "cache-image-snip.ss" "mrlib")' as well as the posn library.
|
||||||
|
(The module specs are needed for sharing module instantiations, for
|
||||||
|
example, without the above, posn values in testing code will be a
|
||||||
|
different type from posn values in tested code.)
|
||||||
|
|
||||||
|
> sandbox-reader
|
||||||
|
A parameter that holds a function that reads all expressions from
|
||||||
|
the current-input-port. It is used to read the submission source.
|
||||||
|
It must return a list of syntax objects, and it must use the symbol
|
||||||
|
`program' as the input source (that is, something like a loop that
|
||||||
|
consumes the input using `(read-syntax 'program)'). The default
|
||||||
|
reader is using a plain `read-syntax' -- it does so while setting
|
||||||
|
`read-case-sensitive' to `#t', and `read-decimal-as-inexact' to `#f'
|
||||||
|
(both are sensible choices for testing code).
|
||||||
|
|
||||||
|
> sandbox-security-guard
|
||||||
|
A parameter that holds a security guard that is used by all
|
||||||
|
evaluations that happen in a `make-evaluator' function. The default
|
||||||
|
value is a security guard that forbids writing, deleting, execution,
|
||||||
|
acessing any paths outside of the collection paths, or any kind of
|
||||||
|
network activity.
|
||||||
|
|
||||||
|
> (make-evaluator language teachpack-paths input-program)
|
||||||
|
This is the main entry point for the sandbox module.
|
||||||
|
|
||||||
|
This function Creates an evaluator function for evaluating
|
||||||
|
expressions in the designated `language', after loading teachpacks
|
||||||
|
that are specified in `teachpack-paths', and after evaluating the
|
||||||
|
code in the `input-program'.
|
||||||
|
|
||||||
|
The `input-program' holds the input program in one of the following
|
||||||
|
ways:
|
||||||
|
* an input port that produces the content of the definitions window;
|
||||||
|
* a string or a byte string that contains the definitions window
|
||||||
|
(you can use "" for an empty definitions window);
|
||||||
|
* a path that names a file holding the input program.
|
||||||
|
The contents of the input program is read using the
|
||||||
|
`sandbox-reader', with line-counting enabled.
|
||||||
|
|
||||||
The `language' can be:
|
The `language' can be:
|
||||||
* a symbol indicating a built-in language (currently, only
|
* a symbol indicating a built-in language (currently, only
|
||||||
|
@ -593,14 +667,34 @@ The _utils.ss_ module provides utilities helpful in implementing
|
||||||
* a list that begins with a 'begin symbol is arbitrary code that is
|
* a list that begins with a 'begin symbol is arbitrary code that is
|
||||||
prefixed into the submitted program.
|
prefixed into the submitted program.
|
||||||
|
|
||||||
The actual evaluation of expressions happens in a newly created
|
The actual evaluation of expressions (both the program and later
|
||||||
eventspace and namespace, and under the supervision of a strict
|
evaluations) happens under the `sandbox-security-guard'
|
||||||
security guard that reading files only from PLT collections, and no
|
restrictions, and if MrEd is used -- in a newly created eventspace.
|
||||||
other operations.
|
See also `with-limits' below for adding resource limits, and
|
||||||
|
`get-uncovered-expressions' above for enforcing test coverage.
|
||||||
|
|
||||||
Additional arguments to the evaluator function are special messages
|
> (with-limits sec mb thunk)
|
||||||
that retrieve additional information. Currently, only
|
This function executes the given thunk with memory and time
|
||||||
'uncovered-expressions is used (see below).
|
restrictions: if execution consumes more than `mb' megabytes or more
|
||||||
|
that `sec' seconds, then the computation is aborted and an error is
|
||||||
|
thrown. Otherwise the result of the thunk is returned (a value,
|
||||||
|
multiple values, or raise an exception). Each of the two limits can
|
||||||
|
be `#f' to disable it.
|
||||||
|
|
||||||
|
(Note: memory limit requires running in a 3m executable; the limit
|
||||||
|
is only checked after a GC happens.)
|
||||||
|
|
||||||
|
|
||||||
|
_utils.ss_
|
||||||
|
----------
|
||||||
|
|
||||||
|
> (get-conf key)
|
||||||
|
Returns a value from the configuration file (useful for reading
|
||||||
|
things like field names etc)
|
||||||
|
|
||||||
|
> (unpack-submission bytes)
|
||||||
|
Returns two text% objects corresponding to the submitted definitions
|
||||||
|
and interactions windows.
|
||||||
|
|
||||||
> (make-evaluator/submission language teachpack-paths bytes)
|
> (make-evaluator/submission language teachpack-paths bytes)
|
||||||
Like `make-evaluator', but the definitions content is supplied as a
|
Like `make-evaluator', but the definitions content is supplied as a
|
||||||
|
@ -626,17 +720,6 @@ The _utils.ss_ module provides utilities helpful in implementing
|
||||||
> (evaluate-submission bytes eval)
|
> (evaluate-submission bytes eval)
|
||||||
Like `load' on a submission byte string.
|
Like `load' on a submission byte string.
|
||||||
|
|
||||||
> coverage-enabled
|
|
||||||
Parameter that controls whether coverage testing is enabled. If it
|
|
||||||
set to true, the handin-server/private/coverage collection will be
|
|
||||||
used to detect uncovered expressions. This information is collected
|
|
||||||
before additional checker-evaluations. To retrieve the collected
|
|
||||||
information, apply the evaluation function with a second argument of
|
|
||||||
'uncovered-expressions (the first argument will be ignored). The
|
|
||||||
resulting value is a list of uncovered expressions, with at most one
|
|
||||||
per position+span (so the contents is unreliable, but the position
|
|
||||||
is).
|
|
||||||
|
|
||||||
> (check-proc eval expect-v compare-proc proc-name arg ...)
|
> (check-proc eval expect-v compare-proc proc-name arg ...)
|
||||||
Calls the function named `proc-name' using the evaluator `eval',
|
Calls the function named `proc-name' using the evaluator `eval',
|
||||||
giving it the (unquoted) arguments `arg'... Let `result-v' be the
|
giving it the (unquoted) arguments `arg'... Let `result-v' be the
|
||||||
|
@ -717,15 +800,15 @@ The _utils.ss_ module provides utilities helpful in implementing
|
||||||
specified), the timer will be reset to the 'session-timeout value.)
|
specified), the timer will be reset to the 'session-timeout value.)
|
||||||
|
|
||||||
|
|
||||||
Extra Checker Utilities
|
_checker.ss_
|
||||||
============================================
|
------------
|
||||||
|
|
||||||
The _extra-utils.ss_ module provides a higher-level of utilities,
|
The "checker.ss" module provides a higher-level of utilities, helpful
|
||||||
helpful in implementing `checker' functions that are intended for a
|
in implementing `checker' functions that are intended for a more
|
||||||
more automated system. This module is a language module -- a typical
|
automated system. This module is a language module -- a typical
|
||||||
checker that uses it looks like this:
|
checker that uses it looks like this:
|
||||||
|
|
||||||
(module checker (lib "extra-utils.ss" "handin-server")
|
(module checker (lib "checker.ss" "handin-server")
|
||||||
(check: :language 'intermediate
|
(check: :language 'intermediate
|
||||||
:users pairs-or-singles-with-warning
|
:users pairs-or-singles-with-warning
|
||||||
:coverage? #t
|
:coverage? #t
|
||||||
|
@ -998,11 +1081,13 @@ value from the submission code.
|
||||||
you can call it earlier (eg, before testing) to show clients a
|
you can call it earlier (eg, before testing) to show clients a
|
||||||
coverage error first.
|
coverage error first.
|
||||||
|
|
||||||
*** Multiple-file submissions
|
|
||||||
|
Multiple-File Submissions:
|
||||||
|
==========================
|
||||||
|
|
||||||
By default, the system is set up for submissions of single a single
|
By default, the system is set up for submissions of single a single
|
||||||
file, straight fom DrScheme using the handin-client. There is some
|
file, straight fom DrScheme using the handin-client. There is some
|
||||||
support for multi-file submissions in "extra-utils.ss" and in the
|
support for multi-file submissions in "checker.ss" and in the
|
||||||
handin-client -- it is possible to submit multiple files, and have the
|
handin-client -- it is possible to submit multiple files, and have the
|
||||||
system generate a single file that is the concatenation of all
|
system generate a single file that is the concatenation of all
|
||||||
submission files (used only with text files). To set up multi-file
|
submission files (used only with text files). To set up multi-file
|
||||||
|
@ -1050,7 +1135,9 @@ submission utility -- the resulting executable can be used outside of
|
||||||
DrScheme (but PLT Scheme is still required, so it cannot be
|
DrScheme (but PLT Scheme is still required, so it cannot be
|
||||||
uninstalled).
|
uninstalled).
|
||||||
|
|
||||||
*** Auto-updater
|
|
||||||
|
Auto-Updater:
|
||||||
|
=============
|
||||||
|
|
||||||
The handin-client has code that can be used for automatic updating of
|
The handin-client has code that can be used for automatic updating of
|
||||||
clients. This can be useful for courses where you distribute some
|
clients. This can be useful for courses where you distribute some
|
||||||
|
|
|
@ -550,13 +550,13 @@
|
||||||
(custodian-limit-memory session-cust
|
(custodian-limit-memory session-cust
|
||||||
(get-conf 'session-memory-limit)
|
(get-conf 'session-memory-limit)
|
||||||
session-cust)))
|
session-cust)))
|
||||||
(let* ([watcher
|
(let ([watcher
|
||||||
(parameterize ([current-custodian orig-custodian])
|
(parameterize ([current-custodian orig-custodian])
|
||||||
(thread
|
(thread
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(let ([session-thread (channel-get session-channel)])
|
(let ([session-thread (channel-get session-channel)])
|
||||||
(let loop ([timed-out? #f])
|
(let loop ([timed-out? #f])
|
||||||
(cond
|
(cond
|
||||||
[(sync/timeout 3 session-thread)
|
[(sync/timeout 3 session-thread)
|
||||||
(let* ([status (unbox status-box)]
|
(let* ([status (unbox status-box)]
|
||||||
[status (if status
|
[status (if status
|
||||||
|
@ -574,9 +574,8 @@
|
||||||
[(let ([t timeout]) ; grab value to avoid races
|
[(let ([t timeout]) ; grab value to avoid races
|
||||||
(and t ((current-inexact-milliseconds) . > . t)))
|
(and t ((current-inexact-milliseconds) . > . t)))
|
||||||
;; Shutdown here to get the handin-terminated error
|
;; Shutdown here to get the handin-terminated error
|
||||||
;; message, instead of relying on
|
;; message, instead of relying on a timeout at the
|
||||||
;; (get-conf 'session-timeout)
|
;; run-server level
|
||||||
;; at the run-server level
|
|
||||||
(custodian-shutdown-all session-cust)
|
(custodian-shutdown-all session-cust)
|
||||||
(loop #t)]
|
(loop #t)]
|
||||||
[else
|
[else
|
||||||
|
|
222
collects/handin-server/sandbox.ss
Normal file
222
collects/handin-server/sandbox.ss
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
(module sandbox mzscheme
|
||||||
|
(require (lib "string.ss") (lib "list.ss"))
|
||||||
|
|
||||||
|
(provide mred?
|
||||||
|
coverage-enabled
|
||||||
|
namespace-specs
|
||||||
|
sandbox-reader
|
||||||
|
sandbox-security-guard
|
||||||
|
get-uncovered-expressions
|
||||||
|
make-evaluator)
|
||||||
|
|
||||||
|
(define mred?
|
||||||
|
(with-handlers ([void (lambda (_) #f)])
|
||||||
|
(dynamic-require '#%mred-kernel #f)
|
||||||
|
#t))
|
||||||
|
(define-syntax mz/mr ; use a value for mzscheme, or pull a mred binding
|
||||||
|
(syntax-rules ()
|
||||||
|
[(mz/mr mzval mrsym)
|
||||||
|
(if mred? (dynamic-require '(lib "mred.ss" "mred") 'mrsym) mzval)]))
|
||||||
|
|
||||||
|
;; Configuration ------------------------------------------------------------
|
||||||
|
|
||||||
|
(define coverage-enabled (make-parameter #f))
|
||||||
|
|
||||||
|
(define namespace-specs
|
||||||
|
(make-parameter
|
||||||
|
(let ([mods '((lib "posn.ss" "lang"))]
|
||||||
|
[mred-mods '((lib "cache-image-snip.ss" "mrlib"))])
|
||||||
|
`(,(mz/mr make-namespace make-namespace-with-mred)
|
||||||
|
,@mods ,@(if mred? mred-mods '())))))
|
||||||
|
|
||||||
|
(define (default-sandbox-reader)
|
||||||
|
(parameterize ([read-case-sensitive #t] [read-decimal-as-inexact #f])
|
||||||
|
(let loop ([l '()])
|
||||||
|
(let ([expr (read-syntax 'program)])
|
||||||
|
(if (eof-object? expr)
|
||||||
|
(reverse! l)
|
||||||
|
(loop (cons expr l)))))))
|
||||||
|
|
||||||
|
(define sandbox-reader (make-parameter default-sandbox-reader))
|
||||||
|
|
||||||
|
(define ok-path-re
|
||||||
|
(byte-regexp
|
||||||
|
(bytes-append
|
||||||
|
#"^(?:"
|
||||||
|
(apply bytes-append
|
||||||
|
(cdr (apply append
|
||||||
|
(map (lambda (p)
|
||||||
|
(list #"|" (regexp-quote (path->bytes p))))
|
||||||
|
(current-library-collection-paths)))))
|
||||||
|
#")(?:/|$)")))
|
||||||
|
|
||||||
|
(define sandbox-security-guard
|
||||||
|
(make-parameter
|
||||||
|
(make-security-guard
|
||||||
|
(current-security-guard)
|
||||||
|
(lambda (what path modes)
|
||||||
|
(when (or (memq 'write modes)
|
||||||
|
(memq 'execute modes)
|
||||||
|
(memq 'delete modes)
|
||||||
|
(and path
|
||||||
|
(not (regexp-match? ok-path-re (path->bytes path)))))
|
||||||
|
(error what "file access denied (~a)" path)))
|
||||||
|
(lambda (what host port mode) (error what "network access denied")))))
|
||||||
|
|
||||||
|
(define null-input (open-input-string ""))
|
||||||
|
(define (safe-eval expr)
|
||||||
|
(parameterize ([current-security-guard (sandbox-security-guard)]
|
||||||
|
[current-input-port null-input]
|
||||||
|
;; breaks: [current-code-inspector (make-inspector)]
|
||||||
|
)
|
||||||
|
(eval expr)))
|
||||||
|
|
||||||
|
;; Execution ----------------------------------------------------------------
|
||||||
|
|
||||||
|
(define (make-evaluation-namespace)
|
||||||
|
(let* ([specs (namespace-specs)]
|
||||||
|
[new-ns ((car specs))]
|
||||||
|
[orig-ns (current-namespace)]
|
||||||
|
[mods (cdr specs)]
|
||||||
|
[resolve (current-module-name-resolver)])
|
||||||
|
(for-each (lambda (mod) (dynamic-require mod #f)) mods)
|
||||||
|
(let ([modsyms (map (lambda (mod) (resolve mod #f #f)) mods)])
|
||||||
|
(parameterize ([current-namespace new-ns])
|
||||||
|
(for-each (lambda (ms) (namespace-attach-module orig-ns ms))
|
||||||
|
modsyms)))
|
||||||
|
new-ns))
|
||||||
|
|
||||||
|
(define (read-code inp)
|
||||||
|
(parameterize ([current-input-port
|
||||||
|
(cond [(input-port? inp) inp]
|
||||||
|
[(string? inp) (open-input-string inp)]
|
||||||
|
[(bytes? inp) (open-input-bytes inp)]
|
||||||
|
[(path? inp) (open-input-file inp)]
|
||||||
|
[else (error 'read-code "bad input: ~e" inp)])])
|
||||||
|
(port-count-lines! current-input-port)
|
||||||
|
((sandbox-reader))))
|
||||||
|
|
||||||
|
(define (evaluate-program language teachpacks input-program uncovered!)
|
||||||
|
(let* ([body (read-code input-program)]
|
||||||
|
[body (append (if (and (pair? teachpacks)
|
||||||
|
(eq? 'begin (car teachpacks)))
|
||||||
|
(cdr teachpacks)
|
||||||
|
(map (lambda (tp)
|
||||||
|
`(,#'require ,(if (pair? tp) tp `(file ,tp))))
|
||||||
|
teachpacks))
|
||||||
|
body)]
|
||||||
|
[body (cond [(and (symbol? language)
|
||||||
|
(memq language '(beginner
|
||||||
|
beginner-abbr
|
||||||
|
intermediate
|
||||||
|
intermediate-lambda
|
||||||
|
advanced)))
|
||||||
|
`(module m
|
||||||
|
(lib ,(case language
|
||||||
|
[(beginner) "htdp-beginner.ss"]
|
||||||
|
[(beginner-abbr) "htdp-beginner-abbr.ss"]
|
||||||
|
[(intermediate) "htdp-intermediate.ss"]
|
||||||
|
[(intermediate-lambda)
|
||||||
|
"htdp-intermediate-lambda.ss"]
|
||||||
|
[(advanced) "htdp-advanced.ss"])
|
||||||
|
"lang")
|
||||||
|
,@body)]
|
||||||
|
[(or (and (pair? language) (eq? 'lib (car language)))
|
||||||
|
(symbol? language))
|
||||||
|
`(module m ,language ,@body)]
|
||||||
|
[(and (pair? language)
|
||||||
|
(eq? 'begin (car language)))
|
||||||
|
`(begin ,language ,@body)]
|
||||||
|
[else (error 'make-evaluator
|
||||||
|
"Bad language specification: ~e"
|
||||||
|
language)])])
|
||||||
|
(when uncovered!
|
||||||
|
(safe-eval '(require (lib "coverage.ss" "handin-server" "private"))))
|
||||||
|
(safe-eval body)
|
||||||
|
(when (and (pair? body) (eq? 'module (car body))
|
||||||
|
(pair? (cdr body)) (symbol? (cadr body)))
|
||||||
|
(let ([mod (cadr body)])
|
||||||
|
(safe-eval `(require ,mod))
|
||||||
|
(current-namespace (module->namespace mod))))
|
||||||
|
(when uncovered!
|
||||||
|
(uncovered! (filter (lambda (x) (eq? 'program (syntax-source x)))
|
||||||
|
(safe-eval '(get-uncovered-expressions)))))))
|
||||||
|
|
||||||
|
(define current-eventspace (mz/mr (make-parameter #f) current-eventspace))
|
||||||
|
(define make-eventspace (mz/mr void make-eventspace))
|
||||||
|
(define run-in-bg (mz/mr thread queue-callback))
|
||||||
|
|
||||||
|
(define get-uncovered-expressions "get-uncovered-expressions")
|
||||||
|
|
||||||
|
(define (make-evaluator language teachpacks input-program)
|
||||||
|
(let ([coverage-enabled (coverage-enabled)]
|
||||||
|
[uncovered-expressions #f]
|
||||||
|
[ns (make-evaluation-namespace)]
|
||||||
|
[input-ch (make-channel)]
|
||||||
|
[result-ch (make-channel)])
|
||||||
|
(parameterize ([current-namespace ns]
|
||||||
|
[current-inspector (make-inspector)]
|
||||||
|
;; bogus parameter and value if we're in mzscheme
|
||||||
|
[current-eventspace (make-eventspace)])
|
||||||
|
(run-in-bg
|
||||||
|
(lambda ()
|
||||||
|
;; First read program and evaluate it as a module:
|
||||||
|
(with-handlers ([void (lambda (exn) (channel-put result-ch exn))])
|
||||||
|
(evaluate-program
|
||||||
|
language teachpacks input-program
|
||||||
|
(and coverage-enabled
|
||||||
|
(lambda (exprs) (set! uncovered-expressions exprs))))
|
||||||
|
(channel-put result-ch 'ok))
|
||||||
|
;; Now wait for interaction expressions:
|
||||||
|
(let loop ()
|
||||||
|
(let ([expr (channel-get input-ch)])
|
||||||
|
(unless (eof-object? expr)
|
||||||
|
(with-handlers ([void (lambda (exn)
|
||||||
|
(channel-put result-ch
|
||||||
|
(cons 'exn exn)))])
|
||||||
|
(channel-put result-ch
|
||||||
|
(cons 'vals (call-with-values
|
||||||
|
(lambda () (safe-eval expr))
|
||||||
|
list))))
|
||||||
|
(loop))))
|
||||||
|
(let loop ()
|
||||||
|
(channel-put result-ch '(exn . no-more-to-evaluate))
|
||||||
|
(loop))))
|
||||||
|
(let ([r (channel-get result-ch)])
|
||||||
|
(if (eq? r 'ok)
|
||||||
|
;; Initial program executed ok, so return an evaluator:
|
||||||
|
(lambda (expr)
|
||||||
|
(if (eq? expr get-uncovered-expressions)
|
||||||
|
uncovered-expressions
|
||||||
|
(begin (channel-put input-ch expr)
|
||||||
|
(let ([r (channel-get result-ch)])
|
||||||
|
(if (eq? (car r) 'exn)
|
||||||
|
(raise (cdr r))
|
||||||
|
(apply values (cdr r)))))))
|
||||||
|
;; Program didn't execute:
|
||||||
|
(raise r))))))
|
||||||
|
|
||||||
|
;; Resources ----------------------------------------------------------------
|
||||||
|
|
||||||
|
(define (with-limits sec mb thunk)
|
||||||
|
(let ([cust (make-custodian)]
|
||||||
|
[ch (make-channel)])
|
||||||
|
(when mb (custodian-limit-memory cust (* mb 1024 1024) cust))
|
||||||
|
(let* ([work (parameterize ([current-custodian cust])
|
||||||
|
(thread (lambda ()
|
||||||
|
(channel-put ch
|
||||||
|
(with-handlers ([void (lambda (e)
|
||||||
|
(list raise e))])
|
||||||
|
(call-with-values thunk
|
||||||
|
(lambda vs (cons values vs))))))))]
|
||||||
|
[watch (thread (lambda ()
|
||||||
|
(channel-put ch
|
||||||
|
(if (sync/timeout sec work) 'memory 'time))))]
|
||||||
|
[r (channel-get ch)])
|
||||||
|
(custodian-shutdown-all cust)
|
||||||
|
(kill-thread watch)
|
||||||
|
(if (list? r)
|
||||||
|
(apply (car r) (cdr r))
|
||||||
|
(error 'with-limit "out of ~a" r)))))
|
||||||
|
|
||||||
|
)
|
|
@ -1,20 +1,21 @@
|
||||||
(module utils mzscheme
|
(module utils mzscheme
|
||||||
(require (lib "class.ss")
|
(require (lib "list.ss")
|
||||||
|
(lib "class.ss")
|
||||||
(lib "mred.ss" "mred")
|
(lib "mred.ss" "mred")
|
||||||
(lib "posn.ss" "lang")
|
(lib "posn.ss" "lang")
|
||||||
"private/run-status.ss"
|
|
||||||
"private/config.ss"
|
|
||||||
(prefix pc: (lib "pconvert.ss"))
|
(prefix pc: (lib "pconvert.ss"))
|
||||||
(lib "pretty.ss")
|
(lib "pretty.ss")
|
||||||
(lib "list.ss")
|
(only "handin-server.ss" timeout-control)
|
||||||
(lib "string.ss")
|
"private/run-status.ss"
|
||||||
(only "handin-server.ss" timeout-control))
|
"private/config.ss"
|
||||||
|
"sandbox.ss")
|
||||||
|
|
||||||
(provide get-conf
|
(provide (all-from "sandbox.ss")
|
||||||
|
|
||||||
|
get-conf
|
||||||
|
|
||||||
unpack-submission
|
unpack-submission
|
||||||
|
|
||||||
make-evaluator
|
|
||||||
make-evaluator/submission
|
make-evaluator/submission
|
||||||
evaluate-all
|
evaluate-all
|
||||||
evaluate-submission
|
evaluate-submission
|
||||||
|
@ -26,8 +27,6 @@
|
||||||
message
|
message
|
||||||
current-value-printer
|
current-value-printer
|
||||||
|
|
||||||
coverage-enabled
|
|
||||||
|
|
||||||
check-proc
|
check-proc
|
||||||
check-defined
|
check-defined
|
||||||
look-for-tests
|
look-for-tests
|
||||||
|
@ -48,170 +47,15 @@
|
||||||
(read-editor-global-footer stream)
|
(read-editor-global-footer stream)
|
||||||
(values definitions-text interactions-text)))
|
(values definitions-text interactions-text)))
|
||||||
|
|
||||||
;; Protection ---------------------------------------
|
|
||||||
|
|
||||||
(define ok-path-re
|
|
||||||
(regexp
|
|
||||||
(string-append
|
|
||||||
"^(?:"
|
|
||||||
(apply string-append
|
|
||||||
(cdr (apply append
|
|
||||||
(map (lambda (p)
|
|
||||||
(list "|" (regexp-quote (path->string p))))
|
|
||||||
(current-library-collection-paths)))))
|
|
||||||
")(?:/|$)")))
|
|
||||||
|
|
||||||
(define tight-security
|
|
||||||
(make-security-guard
|
|
||||||
(current-security-guard)
|
|
||||||
(lambda (what path modes)
|
|
||||||
(when (or (memq 'write modes)
|
|
||||||
(memq 'execute modes)
|
|
||||||
(memq 'delete modes)
|
|
||||||
(and path (not (regexp-match ok-path-re (path->string path)))))
|
|
||||||
(error what "file access denied (~a)" path)))
|
|
||||||
(lambda (what host port mode) (error what "network access denied"))))
|
|
||||||
|
|
||||||
(define null-input (open-input-string ""))
|
|
||||||
(define (safe-eval expr . more)
|
|
||||||
(parameterize ([current-security-guard tight-security]
|
|
||||||
[current-input-port null-input]
|
|
||||||
;; breaks: [current-code-inspector (make-inspector)]
|
|
||||||
)
|
|
||||||
(apply eval expr more)))
|
|
||||||
|
|
||||||
;; Execution ----------------------------------------
|
;; Execution ----------------------------------------
|
||||||
|
|
||||||
(define coverage-enabled (make-parameter #f))
|
|
||||||
|
|
||||||
(define modules-to-attach
|
|
||||||
(list '(lib "posn.ss" "lang")
|
|
||||||
'(lib "cache-image-snip.ss" "mrlib")))
|
|
||||||
|
|
||||||
(define (make-evaluation-namespace)
|
|
||||||
(let ([new-ns (make-namespace-with-mred)]
|
|
||||||
[orig-ns (current-namespace)])
|
|
||||||
(for-each (lambda (mod) (dynamic-require mod #f))
|
|
||||||
modules-to-attach)
|
|
||||||
(let ([modsyms
|
|
||||||
(map (lambda (mod) ((current-module-name-resolver) mod #f #f))
|
|
||||||
modules-to-attach)])
|
|
||||||
(parameterize ((current-namespace new-ns))
|
|
||||||
(for-each (lambda (ms) (namespace-attach-module orig-ns ms))
|
|
||||||
modsyms)))
|
|
||||||
new-ns))
|
|
||||||
|
|
||||||
(define (make-evaluator language teachpacks program-port)
|
|
||||||
(let ([coverage-enabled (coverage-enabled)]
|
|
||||||
[uncovered-expressions #f]
|
|
||||||
[ns (make-evaluation-namespace)]
|
|
||||||
[orig-ns (current-namespace)])
|
|
||||||
(parameterize ([current-namespace ns]
|
|
||||||
[read-case-sensitive #t]
|
|
||||||
[read-decimal-as-inexact #f]
|
|
||||||
[current-inspector (make-inspector)])
|
|
||||||
(parameterize ([current-eventspace (make-eventspace)])
|
|
||||||
(let ([ch (make-channel)]
|
|
||||||
[result-ch (make-channel)])
|
|
||||||
(queue-callback
|
|
||||||
(lambda ()
|
|
||||||
;; First read program and evaluate it as a module:
|
|
||||||
(with-handlers ([void (lambda (exn) (channel-put result-ch (cons 'exn exn)))])
|
|
||||||
(let* ([body
|
|
||||||
(parameterize ([read-case-sensitive #t]
|
|
||||||
[read-decimal-as-inexact #f])
|
|
||||||
(let loop ([l null])
|
|
||||||
(let ([expr (read-syntax 'program program-port)])
|
|
||||||
(if (eof-object? expr)
|
|
||||||
(reverse l)
|
|
||||||
(loop (cons expr l))))))]
|
|
||||||
[body (append (if (and (pair? teachpacks)
|
|
||||||
(eq? 'begin (car teachpacks)))
|
|
||||||
(cdr teachpacks)
|
|
||||||
(map (lambda (tp)
|
|
||||||
`(,#'require
|
|
||||||
,(if (pair? tp)
|
|
||||||
tp `(file ,tp))))
|
|
||||||
teachpacks))
|
|
||||||
body)]
|
|
||||||
[body
|
|
||||||
(cond
|
|
||||||
[(and (symbol? language)
|
|
||||||
(memq language '(beginner
|
|
||||||
beginner-abbr
|
|
||||||
intermediate
|
|
||||||
intermediate-lambda
|
|
||||||
advanced)))
|
|
||||||
`(module m
|
|
||||||
(lib ,(case language
|
|
||||||
[(beginner) "htdp-beginner.ss"]
|
|
||||||
[(beginner-abbr) "htdp-beginner-abbr.ss"]
|
|
||||||
[(intermediate) "htdp-intermediate.ss"]
|
|
||||||
[(intermediate-lambda) "htdp-intermediate-lambda.ss"]
|
|
||||||
[(advanced) "htdp-advanced.ss"])
|
|
||||||
"lang")
|
|
||||||
,@body)]
|
|
||||||
[(or (and (pair? language) (eq? 'lib (car language)))
|
|
||||||
(symbol? language))
|
|
||||||
`(module m ,language ,@body)]
|
|
||||||
[(and (pair? language)
|
|
||||||
(eq? 'begin (car language)))
|
|
||||||
`(begin ,language ,@body)]
|
|
||||||
[else (error 'make-evaluator
|
|
||||||
"Bad language specification: ~e"
|
|
||||||
language)])])
|
|
||||||
(when coverage-enabled
|
|
||||||
(safe-eval '(require (lib "coverage.ss"
|
|
||||||
"handin-server" "private"))))
|
|
||||||
(safe-eval body)
|
|
||||||
(when (and (pair? body) (eq? 'module (car body))
|
|
||||||
(pair? (cdr body)) (symbol? (cadr body)))
|
|
||||||
(let ([mod (cadr body)])
|
|
||||||
(safe-eval `(require ,mod))
|
|
||||||
(current-namespace (module->namespace mod))))
|
|
||||||
(when coverage-enabled
|
|
||||||
(set! uncovered-expressions
|
|
||||||
(filter (lambda (x) (eq? 'program (syntax-source x)))
|
|
||||||
(safe-eval '(get-uncovered-expressions)
|
|
||||||
ns)))))
|
|
||||||
(channel-put result-ch 'ok))
|
|
||||||
;; Now wait for interaction expressions:
|
|
||||||
(let loop ()
|
|
||||||
(let ([expr (channel-get ch)])
|
|
||||||
(unless (eof-object? expr)
|
|
||||||
(with-handlers ([void (lambda (exn)
|
|
||||||
(channel-put result-ch
|
|
||||||
(cons 'exn exn)))])
|
|
||||||
(channel-put result-ch (cons 'val (safe-eval expr))))
|
|
||||||
(loop))))
|
|
||||||
(let loop ()
|
|
||||||
(channel-put result-ch '(exn . no-more-to-evaluate))
|
|
||||||
(loop))))
|
|
||||||
(let ([r (channel-get result-ch)])
|
|
||||||
(if (eq? r 'ok)
|
|
||||||
;; Initial program executed ok, so return an evaluator:
|
|
||||||
(lambda (expr . more)
|
|
||||||
(if (pair? more)
|
|
||||||
(case (car more)
|
|
||||||
[(uncovered-expressions) uncovered-expressions]
|
|
||||||
[else (error 'make-evaluator
|
|
||||||
"Bad arguments: ~e"
|
|
||||||
(cons expr more))])
|
|
||||||
(begin (channel-put ch expr)
|
|
||||||
(let ([r (channel-get result-ch)])
|
|
||||||
(if (eq? (car r) 'exn)
|
|
||||||
(raise (cdr r))
|
|
||||||
(cdr r))))))
|
|
||||||
;; Program didn't execute:
|
|
||||||
(raise (cdr r)))))))))
|
|
||||||
|
|
||||||
(define (open-input-text-editor/lines str)
|
(define (open-input-text-editor/lines str)
|
||||||
(let ([inp (open-input-text-editor str)])
|
(let ([inp (open-input-text-editor str)])
|
||||||
(port-count-lines! inp) inp))
|
(port-count-lines! inp) inp))
|
||||||
|
|
||||||
(define (make-evaluator/submission language teachpacks str)
|
(define (make-evaluator/submission language teachpacks str)
|
||||||
(let-values ([(defs interacts) (unpack-submission str)])
|
(let-values ([(defs interacts) (unpack-submission str)])
|
||||||
(make-evaluator language teachpacks (open-input-text-editor/lines defs))))
|
(make-evaluator language teachpacks (open-input-text-editor defs))))
|
||||||
|
|
||||||
(define (evaluate-all source port eval)
|
(define (evaluate-all source port eval)
|
||||||
(let loop ()
|
(let loop ()
|
||||||
|
@ -228,10 +72,9 @@
|
||||||
|
|
||||||
(define (reraise-exn-as-submission-problem thunk)
|
(define (reraise-exn-as-submission-problem thunk)
|
||||||
(with-handlers ([void (lambda (exn)
|
(with-handlers ([void (lambda (exn)
|
||||||
(error
|
(error (if (exn? exn)
|
||||||
(if (exn? exn)
|
(exn-message exn)
|
||||||
(exn-message exn)
|
(format "exception: ~e" exn))))])
|
||||||
(format "~s" exn))))])
|
|
||||||
(thunk)))
|
(thunk)))
|
||||||
|
|
||||||
;; ----------------------------------------
|
;; ----------------------------------------
|
||||||
|
@ -337,6 +180,6 @@
|
||||||
|
|
||||||
(define (call-with-evaluator/submission lang teachpacks str go)
|
(define (call-with-evaluator/submission lang teachpacks str go)
|
||||||
(let-values ([(defs interacts) (unpack-submission str)])
|
(let-values ([(defs interacts) (unpack-submission str)])
|
||||||
(call-with-evaluator lang teachpacks (open-input-text-editor/lines defs) go)))
|
(call-with-evaluator lang teachpacks (open-input-text-editor defs) go)))
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user