1- from __future__ import absolute_import
2- from cyaron import IO
1+ from __future__ import absolute_import , print_function
2+ from cyaron import IO , log
33from cyaron .utils import *
44from cyaron .consts import *
55from cyaron .graders import CYaRonGraders
66import subprocess
7+ import multiprocessing
78import sys
89from io import open
10+ import os
11+
12+
13+ class CompareMismatch (ValueError ):
14+ def __init__ (self , name , mismatch ):
15+ super (CompareMismatch , self ).__init__ (name , mismatch )
16+ self .name = name
17+ self .mismatch = mismatch
918
1019
1120class Compare :
1221 @staticmethod
13- def __compare_two (name , content , std , grader , ** kwargs ):
22+ def __compare_two (name , content , std , grader ):
1423 (result , info ) = CYaRonGraders .invoke (grader , content , std )
15-
16- info = info if info is not None else ""
1724 status = "Correct" if result else "!!!INCORRECT!!!"
18- print ("%s: %s %s" % (name , status , info ))
19-
20- stop_on_incorrect = kwargs .get ("stop_on_incorrect" , False )
21- custom_dump_data = kwargs .get ("dump_data" , None )
22- if stop_on_incorrect and not result :
23- if custom_dump_data :
24- (dump_name , dump_lambda ) = custom_dump_data
25- with open (dump_name , "w" , newline = '\n ' ) as f :
26- f .write (dump_lambda ())
27-
28- with open ("std.out" , "w" , newline = '\n ' ) as f :
29- f .write (std )
30- with open ("%s.out" % name , "w" , newline = '\n ' ) as f :
31- f .write (content )
32-
33- print ("Relevant files dumped." )
34-
35- sys .exit (0 )
36-
25+ info = info if info is not None else ""
26+ log .debug ("{}: {} {}" .format (name , status , info ))
27+ if not result :
28+ raise CompareMismatch (name , info )
3729
3830 @staticmethod
3931 def __process_file (file ):
@@ -46,53 +38,97 @@ def __process_file(file):
4638 return file , f .read ()
4739
4840 @staticmethod
49- def output (* args , ** kwargs ):
50- if len (args ) == 0 :
51- raise Exception ("You must specify some files to compare." )
52-
53- if "std" not in kwargs :
54- raise Exception ("You must specify a std." )
55- (_ , std ) = Compare .__process_file (kwargs ["std" ])
56-
57- grader = kwargs .get ("grader" , DEFAULT_GRADER )
58- stop_on_incorrect = kwargs .get ("stop_on_incorrect" , False )
41+ def __normal_max_workers (workers ):
42+ if workers is None :
43+ if sys .version_info < (3 , 5 ):
44+ cpu = multiprocessing .cpu_count ()
45+ return cpu * 5 if cpu is not None else 1
46+ return workers
47+
48+ @classmethod
49+ def output (cls , * files , ** kwargs ):
50+ kwargs = unpack_kwargs ('output' , kwargs , ('std' , ('grader' , DEFAULT_GRADER ), ('max_workers' , - 1 ), ('job_pool' , None )))
51+ std = kwargs ['std' ]
52+ grader = kwargs ['grader' ]
53+ max_workers = kwargs ['max_workers' ]
54+ job_pool = kwargs ['job_pool' ]
55+ if (max_workers is None or max_workers >= 0 ) and job_pool is None :
56+ max_workers = cls .__normal_max_workers (max_workers )
57+ try :
58+ from concurrent .futures import ThreadPoolExecutor
59+ with ThreadPoolExecutor (max_workers = max_workers ) as job_pool :
60+ return cls .output (* files , std = std , grader = grader , max_workers = max_workers , job_pool = job_pool )
61+ except ImportError :
62+ pass
63+
64+ def get_std ():
65+ return cls .__process_file (std )[1 ]
66+ if job_pool is not None :
67+ std = job_pool .submit (get_std ).result ()
68+ else :
69+ std = get_std ()
5970
60- for file in args :
61- (file_name , content ) = Compare .__process_file (file )
62- Compare .__compare_two (file_name , content , std , grader , stop_on_incorrect = stop_on_incorrect )
71+ def do ( file ) :
72+ (file_name , content ) = cls .__process_file (file )
73+ cls .__compare_two (file_name , content , std , grader )
6374
64- @ staticmethod
65- def program ( * args , ** kwargs ):
66- if len ( args ) == 0 :
67- raise Exception ( "You must specify some programs to compare." )
75+ if job_pool is not None :
76+ job_pool . map ( do , files )
77+ else :
78+ [ x for x in map ( do , files )]
6879
69- if "input" not in kwargs :
70- raise Exception ("You must specify an input." )
80+ @classmethod
81+ def program (cls , * programs , ** kwargs ):
82+ kwargs = unpack_kwargs ('program' , kwargs , ('input' , ('std' , None ), ('std_program' , None ), ('grader' , DEFAULT_GRADER ), ('max_workers' , - 1 ), ('job_pool' , None )))
7183 input = kwargs ['input' ]
84+ std = kwargs ['std' ]
85+ std_program = kwargs ['std_program' ]
86+ grader = kwargs ['grader' ]
87+ max_workers = kwargs ['max_workers' ]
88+ job_pool = kwargs ['job_pool' ]
89+ if (max_workers is None or max_workers >= 0 ) and job_pool is None :
90+ max_workers = cls .__normal_max_workers (max_workers )
91+ try :
92+ from concurrent .futures import ThreadPoolExecutor
93+ with ThreadPoolExecutor (max_workers = max_workers ) as job_pool :
94+ return cls .program (* programs , input = input , std = std , std_program = std_program , grader = grader , max_workers = max_workers , job_pool = job_pool )
95+ except ImportError :
96+ pass
97+
7298 if not isinstance (input , IO ):
73- raise Exception ( "Input must be an IO instance." )
99+ raise TypeError ( "expect {}, got {}" . format ( type ( IO ). __name__ , type ( input ). __name__ ) )
74100 input .flush_buffer ()
75101 input .input_file .seek (0 )
76102
77- std = None
78- if "std" not in kwargs and "std_program" not in kwargs :
79- raise Exception ("You must specify a std or a std_program." )
80- else :
81- if "std_program" in kwargs :
82- std = make_unicode (subprocess .check_output (kwargs ['std_program' ], shell = True , stdin = input .input_file , universal_newlines = True ))
103+ if std_program is not None :
104+ def get_std ():
105+ return make_unicode (subprocess .check_output (std_program , shell = (not list_like (std_program )), stdin = input .input_file , universal_newlines = True ))
106+ if job_pool is not None :
107+ std = job_pool .submit (get_std ).result ()
83108 else :
84- (_ , std ) = Compare .__process_file (kwargs ["std" ])
85-
86- grader = kwargs .get ("grader" , DEFAULT_GRADER )
87- stop_on_incorrect = kwargs .get ("stop_on_incorrect" , False )
88-
89- for program_name in args :
90- input .input_file .seek (0 )
91- content = make_unicode (subprocess .check_output (program_name , shell = True , stdin = input .input_file , universal_newlines = True ))
92-
93- input .input_file .seek (0 )
94- Compare .__compare_two (program_name , content , std , grader ,
95- stop_on_incorrect = stop_on_incorrect ,
96- dump_data = ("error_input.in" , lambda : input .input_file .read ())) # Lazy dump
97-
98- input .input_file .seek (0 , 2 )
109+ std = get_std ()
110+ elif std is not None :
111+ def get_std ():
112+ return cls .__process_file (std )[1 ]
113+ if job_pool is not None :
114+ std = job_pool .submit (get_std ).result ()
115+ else :
116+ std = get_std ()
117+ else :
118+ raise TypeError ('program() missing 1 required non-None keyword-only argument: \' std\' or \' std_program\' ' )
119+
120+ def do (program_name ):
121+ timeout = None
122+ if list_like (program_name ) and len (program_name ) == 2 and int_like (program_name [- 1 ]):
123+ program_name , timeout = program_name
124+ with open (os .dup (input .input_file .fileno ()), 'r' , newline = '\n ' ) as input_file :
125+ if timeout is None :
126+ content = make_unicode (subprocess .check_output (program_name , shell = (not list_like (program_name )), stdin = input_file , universal_newlines = True ))
127+ else :
128+ content = make_unicode (subprocess .check_output (program_name , shell = (not list_like (program_name )), stdin = input_file , universal_newlines = True , timeout = timeout ))
129+ cls .__compare_two (program_name , content , std , grader )
130+
131+ if job_pool is not None :
132+ job_pool .map (do , programs )
133+ else :
134+ [x for x in map (do , programs )]
0 commit comments