Skip to content
This repository was archived by the owner on Oct 14, 2020. It is now read-only.

Rewrite Rust runner #466

Merged
merged 1 commit into from
Aug 5, 2017
Merged
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
12 changes: 9 additions & 3 deletions docker/rust.docker
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
FROM codewars/base-runner

# Setup env
RUN ln -s /home/codewarrior /workspace

COPY frameworks/rust/skeleton /workspace/rust
RUN chown -R codewarrior:codewarrior /workspace/rust

USER codewarrior
ENV USER=codewarrior HOME=/home/codewarrior

# Install rustup with the Rust v1.15.1 toolchain
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain 1.15.1
# ~/.cargo/env
ENV PATH $HOME/.cargo/bin:$PATH
RUN cd /workspace/rust && cargo build && rm src/lib.rs

USER root
RUN ln -s /home/codewarrior /workspace
ENV NPM_CONFIG_LOGLEVEL warn

WORKDIR /runner
Expand All @@ -26,8 +32,8 @@ COPY test/runners/rust_spec.js test/runners/

USER codewarrior
ENV USER=codewarrior HOME=/home/codewarrior
# ~/.cargo/env
ENV PATH=$HOME/.cargo/bin:$PATH

RUN mocha -t 10000 test/runners/rust_spec.js

ENTRYPOINT ["node"]
9 changes: 8 additions & 1 deletion examples/rust.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
test:
rust:
algorithms:
initial: |-
// Return the two oldest/oldest ages within the vector of ages passed in.
Expand Down Expand Up @@ -69,3 +69,10 @@ test:
format!("Hello {}, my name is {}", your_name, self.name)
}
}

fixture: |-
#[test]
fn greet_is_correct() {
let p = Person { name: "Bill" };
assert_eq!(p.greet("Jack"), "Hello Jack, my name is Bill");
}
7 changes: 7 additions & 0 deletions frameworks/rust/skeleton/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "codewars"
version = "0.0.1"
authors = ["codewarrior"]

[dependencies]
rand = "0.3"
9 changes: 9 additions & 0 deletions frameworks/rust/skeleton/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pub fn add(x: i32, y: i32) -> i32 { x + y }

#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(add(1, 1), 2);
}
}
174 changes: 102 additions & 72 deletions lib/runners/rust.js
Original file line number Diff line number Diff line change
@@ -1,91 +1,121 @@
'use strict';

const shovel = require('../shovel');
const util = require('../util');
const exec = require('child_process').exec;

let usingTests = false;
const fs = require('fs');

module.exports.run = function run(opts, cb) {
shovel.start(opts, cb, {
solutionOnly: function(runCode, fail) {
usingTests = false;

const code = `${opts.setup ? opts.setup+'\n' : ''}${opts.code}`;
util.codeWriteSync('rust', code, opts.dir, 'main.rs', true);

_compile((err,stdout,stderr) => {
if (err) return fail({stdout, stderr});

// Run
runCode({
name: `./main`,
options: {
cwd: opts.dir
}
});
fs.writeFileSync('/workspace/rust/src/main.rs',
`${opts.setup ? opts.setup+'\n' : ''}${opts.solution}`);
runCode({
name: 'cargo',
args: ['run'],
options: {
cwd: '/workspace/rust',
env: Object.assign({}, process.env, {
RUSTFLAGS: rustFlags(),
}),
},
});
},
testIntegration: function(runCode, fail) {
usingTests = true;

testIntegration: function(runCode, fail) {
if (opts.testFramework != 'rust')
throw 'Test framework is not supported';

const code = `${opts.setup ? opts.setup+'\n' : ''}${opts.code}\n${opts.fixture}`;
util.codeWriteSync('rust', code, opts.dir, 'main.rs', true);

_compile((err,stdout,stderr) => {
if (err) return fail({stdout, stderr});

// Run
runCode({
name: `./main`,
options: {
cwd: opts.dir
}
});
throw new Error(`Test framework '${opts.testFramework}' is not supported`);

//
// needs some hack in order to maintain backward compatibility and avoid errors with duplicate imports
// .
// |- Cargo.toml
// `- src
// |- lib.rs (opt.setup + opts.solution + module declaration)
// `- tests.rs (opts.fixture)
// - placing `opts.fixture` in submodule `tests.rs` should avoid issues with duplicate imports
// - maintains backward compatibility by prepending `use super::*;` to `opts.fixture` if missing
//
fs.writeFileSync('/workspace/rust/src/lib.rs',
`${opts.setup ? opts.setup+'\n' : ''}${opts.solution}\n#[cfg(test)]\nmod tests;`);
var fixture = opts.fixture;
if (!fixture.includes('use super::*;')) { // HACK backward compatibility
fixture = 'use super::*;\n' + fixture;
}
fs.writeFileSync('/workspace/rust/src/tests.rs', fixture);
runCode({
name: 'cargo',
args: ['test'],
options: {
cwd: '/workspace/rust',
env: Object.assign({}, process.env, {
RUSTFLAGS: rustFlags(),
}),
},
});
},
sanitizeStdOut: function(stdout) {
if (opts.fixture) return _formatOut(stdout);
return stdout;
},

transformBuffer: function(buffer) {
if (opts.testFramework == 'rust') {
// if tests failed then just output the entire raw test spec results so that the full details can be viewed
if (!buffer.stderr && buffer.stdout.indexOf('FAILED') > 0 && buffer.stdout.indexOf('failures:') > 0) {
buffer.stderr = buffer.stdout.substr(buffer.stdout.indexOf('failures:') + 10).replace("note: Run with `RUST_BACKTRACE=1` for a backtrace.", '');
// trim after the first failures section
buffer.stderr = "Failure Info:\n" + buffer.stderr.substr(0, buffer.stderr.indexOf('failures:'));
if (opts.setup) {
buffer.stderr += "\nNOTE: Line numbers reported within errors will not match up exactly to those shown within your editors due to concatenation.";
}
}
if (!opts.fixture) return;

const ss = buffer.stdout.split(/\n((?:---- \S+ stdout ----)|failures:)\n/);
// Save test failures in object to use in output.
// There shouldn't be name collisions since test cases are rust functions.
const failures = {};
for (let i = 1; i < ss.length; ++i) {
const s = ss[i];
const m = s.match(/^---- (\S+) stdout ----$/);
if (m === null) continue;
const name = m[1];
const fail = [];
const x = ss[++i];
const m2 = x.match(/thread '[^']+' panicked at '(.+)'.*/);
if (m2.index != 1) fail.push(x.slice(1, m2.index)); // user logged output
fail.push(`<FAILED::>Test Failed<:LF:>${m2[1].replace(/\\'/g, "'")}`);
failures[name] = fail;
}
}
});

// Initalizes the Rust dir via cargo
const _compile = function(cb) {
exec(`rustc main.rs ${usingTests ? '--test' : ''}`,{cwd: opts.dir}, cb);
};

const _formatOut = function(stdout) {
let output = '';
let tests = stdout.split(/\n/gm).filter(v => !v.search(/^test.*(?:ok|FAILED)$/));

for (let test of tests) output += _parseTest(test);

return output;
};
const out = [];
for (const s of ss[0].split('\n')) {
const m = s.match(/^test (\S+) \.{3} (FAILED|ok)$/);
if (m === null) continue;
out.push(`<IT::>${m[1].replace(/^tests::/, '')}`);
if (m[2] == 'ok') {
out.push('<PASSED::>Test Passed');
}
else {
out.push.apply(out, failures[m[1]]);
}
out.push(`<COMPLETEDIN::>`);
}
out.push('');
buffer.stdout = out.join('\n');
},

const _parseTest = function(test) {
let result = test.split(' ');
let out = `<DESCRIBE::>${result[1]}\n`;
out += result[3] != 'FAILED' ? `<PASSED::>Test Passed\n` : `<FAILED::>Test Failed\n`;
return out;
};
sanitizeStdErr: function(stderr) {
// remove logs from cargo test
stderr = stderr
.replace(/^\s+Compiling .*$/gm, '')
.replace(/^\s+Finished .*$/m, '')
.replace(/^\s+Running .*$/m, '')
.replace(/^\s+Doc-tests .*$/m, '')
.replace('To learn more, run the command again with --verbose.', '')
.replace(/^error: test failed$/m, '')
.replace(/^error: Could not compile .*$/m, '')
.trim();
if (stderr !== '') stderr += '\n';
if (/^error: /m.test(stderr) && opts.setup) {
stderr += "\nNOTE: Line numbers reported within errors will not match up exactly to those shown within your editors due to concatenation.\n";
}
return stderr;
},
});
};


// ignore some warnings for backward compatibility
function rustFlags() {
const flags = [];
if (process.env.RUSTFLAGS !== '') flags.push(process.env.RUSTFLAGS);
flags.push('-A', 'dead_code');
flags.push('-A', 'unused_imports');
flags.push('-A', 'unused_variables');
return flags.join(' ');
}
Loading