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
3
3
from cyaron .utils import *
4
4
from cyaron .consts import *
5
5
from cyaron .graders import CYaRonGraders
6
6
import subprocess
7
+ import multiprocessing
7
8
import sys
8
9
from 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
9
18
10
19
11
20
class Compare :
12
21
@staticmethod
13
- def __compare_two (name , content , std , grader , ** kwargs ):
22
+ def __compare_two (name , content , std , grader ):
14
23
(result , info ) = CYaRonGraders .invoke (grader , content , std )
15
-
16
- info = info if info is not None else ""
17
24
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 )
37
29
38
30
@staticmethod
39
31
def __process_file (file ):
@@ -46,53 +38,97 @@ def __process_file(file):
46
38
return file , f .read ()
47
39
48
40
@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 ()
59
70
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 )
63
74
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 )]
68
79
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 )))
71
83
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
+
72
98
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__ ) )
74
100
input .flush_buffer ()
75
101
input .input_file .seek (0 )
76
102
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 ()
83
108
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