diff --git a/.travis.yml b/.travis.yml
index 4e856402..521036d0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,7 +10,7 @@ addons:
- g++-4.8
- ca-certificates
env:
- - RACKET_DIR=$HOME/racket RACKET_VERSION=6.1.1 NODE_PATH=/usr/local/lib/node_modules
+ - RACKET_DIR=$HOME/racket RACKET_VERSION=6.3 NODE_PATH=/usr/local/lib/node_modules
before_install:
# Get Racket
- git clone https://github.com/greghendershott/travis-racket.git >/dev/null
diff --git a/src/collects/seashell-cli.rkt b/src/collects/seashell-cli.rkt
index 32187103..ad4bf1e2 100644
--- a/src/collects/seashell-cli.rkt
+++ b/src/collects/seashell-cli.rkt
@@ -12,10 +12,10 @@
(define RUN-TIMEOUT (make-parameter #f))
(define-values (project-dir main-file test-name out-file err-file)
(command-line
- #:usage-help "Seashell command-line runner. Return codes:\n 10 means failed compilation\n 20 means the program crashed at runtime\n 30 means the program failed its test\n 40 means the program passed its test"
+ #:usage-help "Seashell command-line tester. Return codes:\n 10 means failed compilation.\n 20 means the program crashed at runtime.\n 30 means the program failed its test.\n 40 means the program passed its test."
#:once-each
[("-t" "--timeout") timeout
- "Override the default seashell timeout (seconds)"
+ "Override the default seashell timeout (seconds)."
(RUN-TIMEOUT (string->number timeout))]
#:args (project-dir main-file test-name out-file err-file)
(values project-dir main-file test-name out-file err-file)))
@@ -23,6 +23,13 @@
(when (RUN-TIMEOUT)
(config-set! 'program-run-timeout (RUN-TIMEOUT)))
+(define temp-dir-path (make-temporary-file "seashell-runtime-~a" 'directory))
+(define default-exit-handler (exit-handler))
+(exit-handler (lambda (exit-code)
+ (delete-directory/files temp-dir-path #:must-exist? #f)
+ (default-exit-handler exit-code)))
+(config-set! 'runtime-files-path temp-dir-path)
+
(define/contract (write-outputs stdout stderr)
(-> (or/c bytes? #f) (or/c bytes? #f) void?)
(when stdout
diff --git a/src/collects/seashell-config.rkt.in b/src/collects/seashell-config.rkt.in
index 7ee79553..6a431590 100644
--- a/src/collects/seashell-config.rkt.in
+++ b/src/collects/seashell-config.rkt.in
@@ -168,6 +168,8 @@
"-gdwarf-4" "-O0" "-mdisable-fp-elim" "-dwarf-column-info"
;; Need this to detect global buffer overflow
"-fno-common"
+ ;; Standard compilation mode
+ "-std=c99"
; Add standard static analyzer options (clang/lib/Driver/Tools.cpp:2954)
;; "-analyzer-store=region"
;; "-analyzer-opt-analyze-nested-blocks"
@@ -185,6 +187,8 @@
(cons 'host '("localhost"))
;; Location of per-user configuration directory.
(cons 'seashell (build-path (find-system-path 'home-dir) ".seashell"))
+ ;; Location of the runtime-files directory.
+ (cons 'runtime-files-path (build-path (find-system-path 'home-dir) ".seashell" "runtime-files"))
;; Name of credentials file.
(cons 'seashell-creds-name (format "seashell~a-creds" @SEASHELL_API_VERSION@))
;; Name of credentials cookie
diff --git a/src/collects/seashell/backend/project.rkt b/src/collects/seashell/backend/project.rkt
index 44b59ae2..8f30ef56 100644
--- a/src/collects/seashell/backend/project.rkt
+++ b/src/collects/seashell/backend/project.rkt
@@ -146,7 +146,7 @@
;; Gets the path where runtime files are stored.
(define/contract (runtime-files-path)
(-> path?)
- (build-path (read-config 'seashell) "runtime-files"))
+ (build-path (read-config 'runtime-files-path)))
;; (init-projects)
;; Creates the directories for projects
diff --git a/src/collects/seashell/websocket/server.rkt b/src/collects/seashell/websocket/server.rkt
index 6cb16d35..351553ad 100644
--- a/src/collects/seashell/websocket/server.rkt
+++ b/src/collects/seashell/websocket/server.rkt
@@ -18,6 +18,7 @@
;; along with this program. If not, see .
(require racket/contract
racket/unit
+ racket/tcp
net/tcp-sig
net/url
(prefix-in raw: net/tcp-unit)
@@ -28,7 +29,6 @@
web-server/private/connection-manager
web-server/private/timer
racket/async-channel
- unstable/contract
seashell/websocket/connection
seashell/websocket/handshake)
@@ -119,7 +119,7 @@
#:tcp@
(unit/c (import) (export tcp^))
#:port
- tcp-listen-port?
+ listen-port-number?
#:listen-ip
(or/c string? false/c)
#:max-waiting
diff --git a/src/frontend/css/common.css b/src/frontend/css/common.css
index 5e37dd1f..8ed4e031 100644
--- a/src/frontend/css/common.css
+++ b/src/frontend/css/common.css
@@ -94,7 +94,7 @@ body {
font-size: 1.2em;
padding-top: 0;
padding-left: 0;
- padding-right: 5px;
+ padding-right: 1em;
text-decoration: none;
font-variant: small-caps;
}
diff --git a/src/frontend/frontend/directives.js b/src/frontend/frontend/directives.js
index 9be4c7bf..668e1b15 100644
--- a/src/frontend/frontend/directives.js
+++ b/src/frontend/frontend/directives.js
@@ -46,10 +46,27 @@ angular.module('frontend-app')
}
};
}])
- .directive('focusOn', ['$timeout', function($timeout) {
- return function(scope, elem, attr) {
- scope.$on(attr.focusOn, function(e) {
- $timeout(function () {elem[0].focus();});
+ .directive('focusOn', ['$timeout', function ($timeout) {
+ return function (scope, elem, attr) {
+ scope.$on(attr.focusOn, function (e) {
+ $timeout(function () {
+ elem[0].focus();
});
- };
- }]);
+ });
+ };
+ }])
+ .directive('focusMe', function ($timeout, $parse) {
+ return {
+ link: function (scope, element, attrs) {
+ var model = $parse(attrs.focusMe);
+ scope.$watch(model, function (value) {
+ if (value === true) {
+ $timeout(function () {
+ element[0].focus();
+ });
+ }
+ });
+ }
+ };
+ });
+
diff --git a/src/frontend/frontend/file.js b/src/frontend/frontend/file.js
index bffe4f28..451082bb 100644
--- a/src/frontend/frontend/file.js
+++ b/src/frontend/frontend/file.js
@@ -34,7 +34,7 @@ angular.module('frontend-app')
self.project = openProject;
self.question = openQuestion;
self.folder = openFolder;
- self.file = openFile;
+ self.file = openFile;
self.console = Console;
self.settings = settings;
self.undoHistory = undoHistory;
@@ -44,6 +44,8 @@ angular.module('frontend-app')
self.isBinaryFile = false;
self.ready = false;
self.ext = self.file.split(".")[1];
+ self.runnerFile = false; // true if a runner file is present in the project
+ self.isFileToRun = false; // true if the current file is the runner file
self.editor = null;
self.timeout = null;
self.loaded = false;
@@ -289,7 +291,7 @@ angular.module('frontend-app')
self.runFile();
}
}, {
- combo: 'ctrl+u',
+ combo: 'ctrl+e',
description: "Starts Tests",
allowIn: ['INPUT', 'SELECT', 'TEXTAREA'],
callback: function (evt) {
@@ -425,14 +427,15 @@ angular.module('frontend-app')
if(!self.console.PIDs) {
return $q.when();
}
- return $q.all(_.map(self.console.PIDs, function(id) {
+ var p = $q.all(_.map(self.console.PIDs, function(id) {
return self.project.kill(id);
}))
.catch(function (error) {
errors.report(error, "Could not stop program!");
- self.console.PIDs = null;
- self.console.running = false;
});
+ self.console.running = false;
+ self.console.PIDs = null;
+ return p;
};
self.indentAll = function() {
@@ -483,11 +486,12 @@ angular.module('frontend-app')
.then(function () {
$scope.$emit('setFileToRun', []);
self.runnerFile = true;
+ self.isFileToRun = true;
})
.catch(function (error) {
errors.report(error, "Could not set runner file!");
});
-
+
// emit an event to the parent scope for
// since EditorController is in the child scope of EditorFileController
@@ -525,11 +529,12 @@ angular.module('frontend-app')
function has_ext(ext, fname){
return fname.split(".").pop() === ext;
}
-
+
self.refreshRunner = function () {
self.project.getFileToRun(self.question)
- .then(function (result) {
+ .then(function (result) {
self.runnerFile = (result !== "");
+ self.isFileToRun = (result === self.file);
});
};
self.refreshRunner();
diff --git a/src/frontend/frontend/modals.js b/src/frontend/frontend/modals.js
index 92ab8acd..943a8d2b 100644
--- a/src/frontend/frontend/modals.js
+++ b/src/frontend/frontend/modals.js
@@ -238,6 +238,52 @@ angular.module('frontend-app')
}).result;
};
}])
+
+ .factory('NewTestModal', ['$modal', 'error-service',
+ function ($modal, errors) {
+ return function(project, question, notify) {
+ notify = notify || function () {};
+ return $modal.open({
+ templateUrl: "frontend/templates/new-test-template.html",
+ controller: ['$scope', '$state', 'error-service', '$q',
+ function($scope, $state, errors, $q) {
+ $scope.new_file_name = "";
+ $scope.question = question;
+ $scope.inputError = false;
+ $scope.newTest = function () {
+ // three cases:
+ // a .in or .expect file, which we create normally
+ // a file without an extension, for which we create a pair
+ // an invalid extension
+ var filename = $scope.new_file_name;
+ var extension = filename.split('.').pop();
+ var results = [];
+ if (extension === 'in' || extension === 'expect') {
+ results.push(project.createFile("tests", question, filename));
+ } else if (filename.split('.').length < 2) {
+ // no extension
+ results.push(project.createFile("tests", question, filename + ".in"));
+ results.push(project.createFile("tests", question, filename + ".expect"));
+ } else {
+ $scope.inputError = "Invalid test file name.";
+ return false;
+ }
+ _.each(results, function (result) {
+ result.then(function () {
+ notify(false, true, project, question, $scope.new_file_folder, filename);
+ }).catch(function (error) {
+ notify(false, false, project, question, $scope_new_file_folder, filename);
+ });
+ });
+ $scope.$close();
+
+ };
+ }]
+ }).results;
+ };
+ }])
+
+
// Directive for New Question Modal Service
.factory('NewQuestionModal', ['$modal', 'error-service',
function ($modal, errors){
diff --git a/src/frontend/frontend/question.js b/src/frontend/frontend/question.js
index 1f869401..7c1dfe93 100644
--- a/src/frontend/frontend/question.js
+++ b/src/frontend/frontend/question.js
@@ -22,10 +22,10 @@
angular.module('frontend-app')
// Editor Controller
.controller("EditorController", ['$state', 'openQuestion', '$scope', 'error-service',
- 'openProject', 'NewFileModal', 'SubmitMarmosetModal', '$interval', 'marmoset',
+ 'openProject', 'NewFileModal', 'NewTestModal', 'SubmitMarmosetModal', '$interval', 'marmoset',
'NewQuestionModal', 'MarmosetResultsModal', 'console-service',
function ($state, openQuestion, $scope, errors,
- openProject, newFileModal, submitMarmosetModal,
+ openProject, newFileModal, newTestModal, submitMarmosetModal,
$interval, marmoset, newQuestionModal,
marmosetResultsModal, Console) {
var self = this;
@@ -153,6 +153,14 @@ angular.module('frontend-app')
});
};
+ /** Adds a pair of .in and .expect files to the project
+ */
+ self.add_test = function () {
+ newTestModal(self.project, self.question, function () {
+ self.refresh();
+ });
+ };
+
/** Dispatches a function to run when the
* current file is saved.
*
diff --git a/src/frontend/frontend/templates/new-file-template.html b/src/frontend/frontend/templates/new-file-template.html
index 0a610310..7718662a 100644
--- a/src/frontend/frontend/templates/new-file-template.html
+++ b/src/frontend/frontend/templates/new-file-template.html
@@ -19,6 +19,7 @@
New file...
name="new_file_name"
class="form-control"
placeholder="(file name)"
+ focus-me="true"
style="outline: none; width: 200px; padding-left: 5px">
{{inputError}}
diff --git a/src/frontend/frontend/templates/new-project-template.html b/src/frontend/frontend/templates/new-project-template.html
index 15a1cb20..3f9d6ec2 100644
--- a/src/frontend/frontend/templates/new-project-template.html
+++ b/src/frontend/frontend/templates/new-project-template.html
@@ -10,7 +10,7 @@
ng-class="{'has-error': newProjectForm.new_project_name.$invalid}">
-
+
Required
diff --git a/src/frontend/frontend/templates/new-test-template.html b/src/frontend/frontend/templates/new-test-template.html
new file mode 100644
index 00000000..c06d9e94
--- /dev/null
+++ b/src/frontend/frontend/templates/new-test-template.html
@@ -0,0 +1,32 @@
+
+