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

Allow Resource Files In Addition To Source Files #25

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion cmd/xaqt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ func evalCode(w http.ResponseWriter, r *http.Request) {
}

// log.Println("submission: ", submission)
stdouts, msg := context.Evaluate(submission.Language, submission.Code, submission.Stdins)
code := xaqt.Code{IsFile: false, String: submission.Code}
stdouts, msg := context.Evaluate(submission.Language, code, submission.Stdins)
log.Println(stdouts, msg)

if len(stdouts) == 0 {
Expand Down
5 changes: 5 additions & 0 deletions constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package xaqt

const (
STDIN_FILENAME = "inputFile" // filename of input provided to user program
)
6 changes: 3 additions & 3 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func NewContext(compilers Compilers, options ...option) (*Context, error) {
}

// Evaluate code in a given language and for a set of 'stdin's.
func (c *Context) Evaluate(language, code string, stdins []string) ([]string, Message) {
func (c *Context) Evaluate(language string, code Code, stdins []string) ([]string, Message) {
stdinGlob := glob(stdins)
results, msg := c.run(language, code, stdinGlob)

Expand All @@ -59,7 +59,7 @@ func (c *Context) Evaluate(language, code string, stdins []string) ([]string, Me

// input is n test calls seperated by newlines
// input and expected MUST end in newlines
func (c *Context) run(language, code, stdinGlob string) (string, Message) {
func (c *Context) run(language string, code Code, stdinGlob string) (string, Message) {
log.Printf("launching new %s sandbox", language)
// log.Printf("launching sandbox...\nLanguage: %s\nStdin: %sCode: Hidden\n", language, stdinGlob)

Expand All @@ -68,7 +68,7 @@ func (c *Context) run(language, code, stdinGlob string) (string, Message) {
return "", Message{"error", "language not supported"}
}

if code == "" {
if !code.IsFile && code.String == "" {
return "", Message{"error", "no code submitted"}
}

Expand Down
8 changes: 7 additions & 1 deletion context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
var box *xaqt.Context
var tests map[string]string

// these are high level functional tests which test the user facing API.

// Test that each compiler, given the appropriate code, can print "Hello"
func TestCompilers(t *testing.T) {
langResults := make(map[string]bool)
Expand All @@ -27,9 +29,13 @@ func TestCompilers(t *testing.T) {
}
}

func printsHello(t *testing.T, lang, code string) bool {
func printsHello(t *testing.T, lang, codeString string) bool {
stdin := ""
expected := "Hello"
code := xaqt.Code{
IsFile: false,
String: codeString,
}
stdouts, msg := box.Evaluate(lang, code, []string{stdin})
//log.Println(stdouts[0], msg)

Expand Down
21 changes: 21 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package xaqt

import (
"os"
"testing"

"github.com/stretchr/testify/suite"
)

// entry point for all package internal tests.
// tests are grouped into suites according to the code being tested.
//
func TestMain(m *testing.M) {
retCode := m.Run()

os.Exit(retCode)
}

func TestSandboxSuite(t *testing.T) {
suite.Run(t, &SandboxTestSuite{})
}
140 changes: 123 additions & 17 deletions sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type sandbox struct {
// sandbox id (uuidV4)
ID string
language ExecutionDetails
code string
code Code
stdin string
options options
// docker client connection
Expand All @@ -38,9 +38,20 @@ type sandbox struct {
errChan <-chan error
}

// describes how the user Code is represented/structured. either it is in memory as a string,
// or it has been persisted to a file. additionally, the user can include resource files.
//
type Code struct {
IsFile bool // is the code in a file or a string
String string // the code represented as a string
SourceFileName string // the name of the src file the code has been written to
ResourceFileNames []string // file names for resources used by source file
Path string // the path to the src file and possibly resource files
}

// constructs a new sandbox given...
//
func newSandbox(l ExecutionDetails, code, stdin string, opts options) (*sandbox, error) {
func newSandbox(l ExecutionDetails, code Code, stdin string, opts options) (*sandbox, error) {
var (
s *sandbox
err error
Expand Down Expand Up @@ -136,7 +147,7 @@ func (s *sandbox) prepare() error {
// and execution payload) into a temporary directory.
//
func (s *sandbox) PrepareTmpDir() error {
// create tmp directory for keeping all code and inputs
// create tmp directory for keeping all code, inputs, and results
tmpFolder, err := ioutil.TempDir(s.options.folder, TmpDirPrefix)
if err != nil {
return err
Expand All @@ -157,12 +168,34 @@ func (s *sandbox) PrepareTmpDir() error {
return err
}

// write source file into tmp dir
// TODO (cw|4.29.2018) we should be able to write an arbitrary number of files
// to the tmp dir.
err = ioutil.WriteFile(tmpFolder+"/"+s.language.SourceFile, []byte(s.code), 0777)
if err != nil {
return err
// write source file and possibly resource files into tmp dir
switch s.code.IsFile {
case true:
// copy source file into tmp dir
err = s.copyFile(
filepath.Join(s.code.Path, s.code.SourceFileName),
filepath.Join(tmpFolder, s.code.SourceFileName),
)
if err != nil {
return err
}

// copy resource files into tmp dir
for _, ResourceFileName := range s.code.ResourceFileNames {
err = s.copyFile(
filepath.Join(s.code.Path, ResourceFileName),
filepath.Join(tmpFolder, ResourceFileName),
)
if err != nil {
return err
}
}
case false:
// write source file into tmp dir
err = ioutil.WriteFile(tmpFolder+"/"+s.language.SourceFile, []byte(s.code.String), 0777)
if err != nil {
return err
}
}

// write a file for stdin
Expand Down Expand Up @@ -260,8 +293,9 @@ func (s *sandbox) execute() (string, error) {
ctx = context.Background()
err error
)
// delete temporary directory once we have finished execution
defer os.RemoveAll(s.options.folder)

// defer cleanup
defer s.cleanup()

// okay lets start the container...
err = s.docker.ContainerStart(
Expand Down Expand Up @@ -312,6 +346,68 @@ func (s *sandbox) execute() (string, error) {
}
}

func (s *sandbox) cleanup() {
// optionally rewrite source and resource files
if true {
err := s.rewriteUserFiles()
if err != nil {
log.Fatalf("unable to rewrite files to %s", s.code.Path)
}
}

// delete temporary directory once we have finished execution
os.RemoveAll(s.options.folder)
}

// overwrites the original source and resource files supplied by the user with those from
// the tmp execution directory after execution has successfully completed.
// you may be wondering:
// (1) Why would we ever want to do this?
// In situations where the code the user supplies modifies itself or its resource files
// and we want these changes to persist.
// (2) When would this situation ever occur?
// If the user code will be run multiple times and must maintain state between calls. A
// perfect example is a machine learning model which needs to update its parameters
// between subsequent calls.
//
func (s *sandbox) rewriteUserFiles() error {
var (
err error
)

// only proceed if the source code is in a file
if !s.code.IsFile {
return nil
}

// TODO (cw|8.23.2018) add some checks here to ensure that the files aren't
// too large.

// copy tmp source file into original dir
err = s.copyFile(
filepath.Join(s.options.folder, s.code.SourceFileName),
filepath.Join(s.code.Path, s.code.SourceFileName),
)
if err != nil {
return err
}

// copy tmp resource files into original dir
for _, ResourceFileName := range s.code.ResourceFileNames {
err = s.copyFile(
filepath.Join(s.options.folder, ResourceFileName),
filepath.Join(s.code.Path, ResourceFileName),
)
if err != nil {
return err
}
}

return nil
}

// utility for copying the Payload dir.
//
func (s *sandbox) copyPayload() error {
source := filepath.Join(s.options.path, "Payload")
dest := filepath.Join(s.options.folder)
Expand All @@ -330,16 +426,26 @@ func (s *sandbox) copyPayload() error {
// read the file
destfile := dest + "/" + file.Name()
sourcefile := source + "/" + file.Name()
bytes, err := ioutil.ReadFile(sourcefile)
err = s.copyFile(sourcefile, destfile)
if err != nil {
return err
}
}

// write the file to tmp
err = ioutil.WriteFile(destfile, bytes, 0777)
if err != nil {
return err
}
return nil
}

// short utility for easily copying files
//
func (s *sandbox) copyFile(src, dest string) error {
bytes, err := ioutil.ReadFile(src)
if err != nil {
return err
}

err = ioutil.WriteFile(dest, bytes, 0777)
if err != nil {
return err
}

return nil
Expand Down
Loading