<commit_before>class PythonEvaluator
  require 'fileutils'

  def self.get_asm_file_path(assign)
    "#{Rails.root}/#{assign.class.to_s}/#{assign.id}/files/"
  end

  def self.create_local_file_for_asm(asm, file)
    dir = get_asm_file_path(asm)

    FileUtils.mkdir_p(dir) unless File.exist?(dir)
    path = File.join(dir, file.original_name)
    file.file.copy_to_local_file :original, path
  end

  def self.get_tmp_file_name(dir, extension = "")
    rand_str = ''
    begin
      rand_str = (0...20).map{ ('a'..'z').to_a[rand(26)] }.join
      full_path = File.join(dir, rand_str + extension)
    end while File.exist?(full_path)
    full_path
  end

  def self.resource_limit(ram, cpu)
"import resource
#resource.setrlimit(resource.RLIMIT_AS, (#{ram}, #{ram}))
resource.setrlimit(resource.RLIMIT_CPU, (#{cpu}, #{cpu}))"
  end

  def self.combine_code(c1, c2)
    c1 << '
' << c2
  end

  def self.add_importing_code(code)
    #change the default directory to current file's directory
'import os
os.chdir(os.path.dirname(os.path.realpath(__file__)))
' << code
  end

  def self.eval_python(dir, code, data, eval = false, resource_limit=false)
    file_path = PythonEvaluator.get_tmp_file_name(dir, ".py")
    code = add_importing_code(code)

    tests = {publicTests: data["publicTests"],
             privateTests:data["privateTests"],
             evalTests:   data["evalTests"]}

    time_limit = data["timeLimitInSec"]
    memory_limit = data["memoryLimitInMB"] * 1024

    if resource_limit
      code = combine_code(resource_limit(memory_limit, time_limit), code)
    end

    FileUtils.mkdir_p(dir) unless File.exist?(dir)
    summary ={publicTests: [],privateTests: [], evalTests: [],errors:[]}
    range = eval ? 2 : 1

    #user could print to stdout, use unique hash to filter out test results
    hash = Digest::MD5.hexdigest(rand().to_s)

    for i in 0..range
      file = File.open(file_path, 'w+')
      #code = code % [memory_limit.to_i * 1024, memory_limit.to_i * 1024, time_limit, time_limit]
      if file
        file.write(code)
        case i
          when 0
            test_type = :publicTests
          when 1
            test_type = :privateTests
          else
            test_type = :evalTests
        end
        test_cases = tests[test_type]
        test_code = ''
        puts tests
        test_cases.each do |test|
          test_code << "\nprint('#{hash} %r' % (#{test["expression"]} == #{test["expected"]}))\n"
        end
        file.write(test_code)
        file.close

        #stdout,stderr,status = Open3.capture3("time python3 #{file_path}")
        #puts "out: ", stdout
        #puts "err: ", stderr
        #puts "status: ", status
        #@stdin,@stdout,@stderr = Open3.pipeline_start("python3 #{file_path}") {|ts|
        #  sleep 2
        #  t = ts[0]
        #  Process.kill("TERM", t.pid)
        #  p t.value #=> #<Process::Status: pid 911 SIGTERM (signal 15)>
        #}
        #@stderr, @stderw = IO.pipe
        @stdout,@stderr, status = Open3.capture3("python3.3 #{file_path}")
        errors = @stderr
        stdout = @stdout
        puts "stdout",stdout
        results = stdout.split("\n").select{|r| r.include? hash }.map{|r| if r.gsub(hash + " ", '').gsub("\n",'') == "True" then true else false end}
        puts "results",results
        File.delete(file_path)

        exec_fail = (!status.success? and errors.length == 0)

        if  status.to_s.include?('(signal 24)')
          errors = "CPU time limit exceeded: running time limit set to #{time_limit} second to prevent possible infinite loop."
        end

        if exec_fail
          errors = "You might have an infinite loop or your recursion level is too deep."
        end

        summary[test_type] = results
        if errors.length > 0
          error_message = errors.scan(/", line \d*.(.*)/m).map{ |m| m}
          error_array = errors.split("\n")
          if error_array.length > 10
            #don't display super long error message
            error_message = error_array.last.split("\n")
          end
          summary[:errors] = exec_fail ? errors : error_message.join
          break<commit_msg>for eval coding test, put test cases in try clauses<commit_after>class PythonEvaluator
  require 'fileutils'

  def self.get_asm_file_path(assign)
    "#{Rails.root}/#{assign.class.to_s}/#{assign.id}/files/"
  end

  def self.create_local_file_for_asm(asm, file)
    dir = get_asm_file_path(asm)

    FileUtils.mkdir_p(dir) unless File.exist?(dir)
    path = File.join(dir, file.original_name)
    file.file.copy_to_local_file :original, path
  end

  def self.get_tmp_file_name(dir, extension = "")
    rand_str = ''
    begin
      rand_str = (0...20).map{ ('a'..'z').to_a[rand(26)] }.join
      full_path = File.join(dir, rand_str + extension)
    end while File.exist?(full_path)
    full_path
  end

  def self.resource_limit(ram, cpu)
"import resource
#resource.setrlimit(resource.RLIMIT_AS, (#{ram}, #{ram}))
resource.setrlimit(resource.RLIMIT_CPU, (#{cpu}, #{cpu}))"
  end

  def self.combine_code(c1, c2)
    c1 << '
' << c2
  end

  def self.add_importing_code(code)
    #change the default directory to current file's directory
'import os
os.chdir(os.path.dirname(os.path.realpath(__file__)))
' << code
  end

  def self.eval_python(dir, code, data, eval = false, resource_limit=false)
    file_path = PythonEvaluator.get_tmp_file_name(dir, ".py")
    code = add_importing_code(code)

    tests = {publicTests: data["publicTests"],
             privateTests:data["privateTests"],
             evalTests:   data["evalTests"]}

    time_limit = data["timeLimitInSec"]
    memory_limit = data["memoryLimitInMB"] * 1024

    if resource_limit
      code = combine_code(resource_limit(memory_limit, time_limit), code)
    end

    FileUtils.mkdir_p(dir) unless File.exist?(dir)
    summary ={publicTests: [],privateTests: [], evalTests: [],errors:[]}
    range = eval ? 2 : 1

    #user could print to stdout, use unique hash to filter out test results
    hash = Digest::MD5.hexdigest(rand().to_s)

    for i in 0..range
      file = File.open(file_path, 'w+')
      #code = code % [memory_limit.to_i * 1024, memory_limit.to_i * 1024, time_limit, time_limit]
      if file
        file.write(code)
        case i
          when 0
            test_type = :publicTests
          when 1
            test_type = :privateTests
          else
            test_type = :evalTests
        end
        test_cases = tests[test_type]
        test_code = ''
        if test_type == :evalTests
          test_cases.each do |test|
            test_code << "\ntry:\n"
            test_code << "    print('#{hash} %r' % (#{test["expression"]} == #{test["expected"]}))\n"
            test_code << "except Exception as e:\n"
            test_code << "    print('#{hash} %r' % False)\n"
          end
        else
          test_cases.each do |test|
            test_code << "\nprint('#{hash} %r' % (#{test["expression"]} == #{test["expected"]}))\n"
          end
        end

        file.write(test_code)
        file.close

        #stdout,stderr,status = Open3.capture3("time python3 #{file_path}")
        #puts "out: ", stdout
        #puts "err: ", stderr
        #puts "status: ", status
        #@stdin,@stdout,@stderr = Open3.pipeline_start("python3 #{file_path}") {|ts|
        #  sleep 2
        #  t = ts[0]
        #  Process.kill("TERM", t.pid)
        #  p t.value #=> #<Process::Status: pid 911 SIGTERM (signal 15)>
        #}
        #@stderr, @stderw = IO.pipe
        @stdout,@stderr, status = Open3.capture3("python3.3 #{file_path}")
        errors = @stderr
        stdout = @stdout
        results = stdout.split("\n").select{|r| r.include? hash }.map{|r| if r.gsub(hash + " ", '').gsub("\n",'') == "True" then true else false end}
        puts results
        File.delete(file_path)

        exec_fail = (!status.success? and errors.length == 0)

        if  status.to_s.include?('(signal 24)')
          errors = "CPU time limit exceeded: running time limit set to #{time_limit} second to prevent possible infinite loop."
        end

        if exec_fail
          errors = "You might have an infinite loop or your recursion level is too deep."
        end

        summary[test_type] = results
        if errors.length > 0
          error_message = errors.scan(/", line \d*.(.*)/m).map{ |m| m}
          error_array = errors.split("\n")
          if error_array.length > 10
            #don't display super long error message
            error_message = error_array.last.split("\n")
          end
          summary[:errors] = exec_fail ? errors : error_message.join
          break
{'fraction_non_alphanumeric': 0.13677389493172876, 'fraction_numerical': 0.01133996760009257, 'mean_word_length': 2.5335241210139, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5693194', 'n_tokens_mistral': 2894, 'n_tokens_neox': 2797, 'n_words': 655}