|
| 1 | +# Lazy hard-coding of problem slugs |
| 2 | +SLUGS = %w{freq streets war password} |
| 3 | +# Number of files to be generated for the server |
| 4 | +TEST_FILES = 50 |
| 5 | +# Number of test cases to include per file (including edge cases) |
| 6 | +# We set this to 200 for the competition |
| 7 | +TEST_CASES = 20 |
| 8 | + |
| 9 | +task :default => [:test] |
| 10 | + |
| 11 | +task :build => [:gen, :test] |
| 12 | + |
| 13 | +task :gen, :slug do |t, args| |
| 14 | + puts "Generating #{ TEST_FILES } files each with #{ TEST_CASES } cases" |
| 15 | + SLUGS.each do |slug| |
| 16 | + if !args[:slug] || args[:slug] == slug |
| 17 | + # Read the edge file |
| 18 | + # We had a problem here where the edge file was not being updated to |
| 19 | + # include the correct number of inputs (aka human error), so people who |
| 20 | + # ignored the number of inputs line got too many solutions. We need to |
| 21 | + # foolproof this more. |
| 22 | + edge = File.open("#{ slug }/edge.in").read.gsub(/^# .*/, '').gsub(/^\s*$\n/, '') |
| 23 | + count = edge.lines.first.to_i |
| 24 | + edge = edge.lines.drop(1).join |
| 25 | + |
| 26 | + # Make the destination folder |
| 27 | + prefix = "grader/#{ slug }/" |
| 28 | + mkpath prefix |
| 29 | + require "./#{ slug }/gen.rb" |
| 30 | + |
| 31 | + print "Generating files for #{ slug }" |
| 32 | + TEST_FILES.times do |i| |
| 33 | + print "." |
| 34 | + file = "#{ prefix }/#{ i }" |
| 35 | + # Generate the input file |
| 36 | + File.open("#{ file }.in", 'w+') do |input| |
| 37 | + # Write the count |
| 38 | + input.puts TEST_CASES |
| 39 | + |
| 40 | + # Write the edge cases |
| 41 | + input.write edge |
| 42 | + |
| 43 | + # Add in our own from gen.rb |
| 44 | + Generator.generate(TEST_CASES - count, :without_leader => true) do |line| |
| 45 | + input.puts line |
| 46 | + end |
| 47 | + end |
| 48 | + |
| 49 | + # Generate the corresponding output file |
| 50 | + File.open("#{ file }.out", 'w+') do |output| |
| 51 | + # Get our solution |
| 52 | + require "./#{ slug }/solution.rb" |
| 53 | + |
| 54 | + Solver.solve(File.open("#{ file }.in")) do |line| |
| 55 | + output.puts line |
| 56 | + end |
| 57 | + end |
| 58 | + end |
| 59 | + puts "done" |
| 60 | + end |
| 61 | + end |
| 62 | +end |
| 63 | + |
| 64 | +require 'open3' |
| 65 | +module Open3 |
| 66 | + # A more encapsulated function that takes a command, optional file to be |
| 67 | + # written to stdin, and a block that stdout is read and passed to. |
| 68 | + def self.piped_input cmd, file=nil, &block |
| 69 | + popen3(cmd) do |stdin, stdout, stderr| |
| 70 | + stdin.write File.open(file).read if file rescue nil |
| 71 | + # stderr raises an exception and prints an error. This could be reworked, |
| 72 | + # since if you rescue the exception you wouldn't want the extra printing. |
| 73 | + unless stderr.eof? |
| 74 | + puts "Error in `#{ cmd } < #{ file }`:" |
| 75 | + print stderr.read |
| 76 | + raise :stderr |
| 77 | + end |
| 78 | + yield stdout.read if block |
| 79 | + end |
| 80 | + end |
| 81 | +end |
| 82 | + |
| 83 | +module Tester |
| 84 | + TESTS = { |
| 85 | + 'freq' => 'freq_Varun.py', |
| 86 | + 'war' => '1DWar_Varun.py', |
| 87 | + 'password' => 'Main.java', |
| 88 | + 'streets' => 'Main.java', |
| 89 | + } |
| 90 | + |
| 91 | + class Base |
| 92 | + def test input |
| 93 | + Open3.piped_input("#{ @cmd } #{ @file }", input) do |output| |
| 94 | + # Write output to temp file |
| 95 | + File.open('.test.diff', 'w+') do |tmp| |
| 96 | + tmp.write output |
| 97 | + end |
| 98 | + |
| 99 | + # Diff with given output |
| 100 | + Open3.piped_input("colordiff -u #{ input.gsub(/in$/, 'out') } .test.diff") do |diffout| |
| 101 | + unless diffout.empty? |
| 102 | + # Diff error! Same thing here with the printing and raising. |
| 103 | + print diffout |
| 104 | + raise "Test failed on #{ input }" |
| 105 | + end |
| 106 | + end |
| 107 | + end |
| 108 | + end |
| 109 | + end |
| 110 | + |
| 111 | + class Java < Base |
| 112 | + def initialize file |
| 113 | + # This is a hacky way to change the directory into the classpath and the |
| 114 | + # file name sans extension into the class name. |
| 115 | + @file = [File.dirname(file), File.basename(file, '.java')].join ' ' |
| 116 | + @cmd = 'java -cp' |
| 117 | + # Compile the java file before the tests run |
| 118 | + Open3.piped_input("javac #{ file }") |
| 119 | + end |
| 120 | + end |
| 121 | + |
| 122 | + class Python < Base |
| 123 | + def initialize file |
| 124 | + @file = file |
| 125 | + @cmd = 'python' |
| 126 | + end |
| 127 | + end |
| 128 | +end |
| 129 | + |
| 130 | +task :test, :slug do |t, args| |
| 131 | + # Try out Varun's solutions |
| 132 | + SLUGS.each do |slug| |
| 133 | + if !args[:slug] || args[:slug] == slug |
| 134 | + # Testing :slug |
| 135 | + print "Testing #{ slug }" |
| 136 | + # Make sure there is a test defined |
| 137 | + test = Tester::TESTS[slug] |
| 138 | + next unless test |
| 139 | + |
| 140 | + # Check file type |
| 141 | + ext = File.extname(test) |
| 142 | + case ext |
| 143 | + when '.java' |
| 144 | + tester = Tester::Java.new("#{ slug }/#{ test }") |
| 145 | + when '.py' |
| 146 | + tester = Tester::Python.new("#{ slug }/#{ test }") |
| 147 | + end |
| 148 | + |
| 149 | + # Do for each file |
| 150 | + TEST_FILES.times do |i| |
| 151 | + tester.test "grader/#{ slug }/#{ i }.in" |
| 152 | + print "." |
| 153 | + end |
| 154 | + puts "done" |
| 155 | + end |
| 156 | + end |
| 157 | + |
| 158 | + # If we get this far, everything succeeded and we can delete this temp file. |
| 159 | + # We don't really want to delete otherwise, since it can be useful for |
| 160 | + # debugging. |
| 161 | + rm '.test.diff' |
| 162 | +end |
0 commit comments