1717import  sys 
1818import  argparse 
1919
20- parser  =  argparse .ArgumentParser (description = "Test runner for typeshed. Patterns are unanchored regexps on the full path." )
20+ parser  =  argparse .ArgumentParser (description = "Test runner for typeshed. " 
21+                                              "Patterns are unanchored regexps on the full path." )
2122parser .add_argument ('-v' , '--verbose' , action = 'count' , default = 0 , help = "More output" )
2223parser .add_argument ('-n' , '--dry-run' , action = 'store_true' , help = "Don't actually run mypy" )
2324parser .add_argument ('-x' , '--exclude' , type = str , nargs = '*' , help = "Exclude pattern" )
25+ parser .add_argument ('-p' , '--python-version' , type = str , nargs = '*' ,
26+                     help = "These versions only (major[.minor])" )
2427parser .add_argument ('filter' , type = str , nargs = '*' , help = "Include pattern (default all)" )
2528
2629
2730def  log (args , * varargs ):
2831    if  args .verbose  >=  2 :
2932        print (* varargs )
3033
34+ 
3135def  match (args , fn ):
3236    if  not  args .filter  and  not  args .exclude :
3337        log (args , fn , 'accept by default' )
@@ -49,6 +53,20 @@ def match(args, fn):
4953    return  True 
5054
5155
56+ def  libpath (major , minor ):
57+     versions  =  ['%d.%d'  %  (major , minor )
58+                 for  minor  in  reversed (range (minor  +  1 ))]
59+     versions .append (str (major ))
60+     versions .append ('2and3' )
61+     paths  =  []
62+     for  v  in  versions :
63+         for  top  in  ['stdlib' , 'third_party' ]:
64+             p  =  os .path .join (top , v )
65+             if  os .path .isdir (p ):
66+                 paths .append (p )
67+     return  paths 
68+ 
69+ 
5270def  main ():
5371    args  =  parser .parse_args ()
5472
@@ -58,39 +76,62 @@ def main():
5876        print ("Cannot import mypy. Did you install it?" )
5977        sys .exit (1 )
6078
61-     files2  =  []
62-     files3  =  []
63-     for  dir , subdirs , files  in  os .walk ('.' ):
64-         for  file  in  files :
65-             if  file  ==  '__builtin__.pyi' :
66-                 continue   # Special case (alias for builtins.py). 
67-             if  file  ==  'typing.pyi' :
68-                 continue   # Hack for https://github.com/python/mypy/issues/1254 
69-             if  file .endswith ('.pyi' ) or  file .endswith ('.py' ):
70-                 full  =  os .path .join (dir , file )
71-                 if  match (args , full ):
72-                     if  '/2'  in  dir :
73-                         files2 .append (full )
74-                     if  '/3'  in  dir  or  '/2and3'  in  dir :
75-                         files3 .append (full )
76-     if  not  (files2  or  files3 ):
77-         print ('--- nothing to do ---' )
79+     versions  =  [(3 , 5 ), (3 , 4 ), (3 , 3 ), (3 , 2 ), (2 , 7 )]
80+     if  args .python_version :
81+         versions  =  [v  for  v  in  versions 
82+                     if  any (('%d.%d'  %  v ).startswith (av ) for  av  in  args .python_version )]
83+         if  not  versions :
84+             print ("--- no versions selected ---" )
85+             sys .exit (1 )
86+ 
7887    code  =  0 
79-     for  flags , files  in  [([], files3 ), (['--py2' ], files2 )]:
88+     runs  =  0 
89+     for  major , minor  in  versions :
90+         roots  =  libpath (major , minor )
91+         files  =  []
92+         seen  =  {'__builtin__' , 'builtins' , 'typing' }  # Always ignore these. 
93+         for  root  in  roots :
94+             names  =  os .listdir (root )
95+             for  name  in  names :
96+                 full  =  os .path .join (root , name )
97+                 mod , ext  =  os .path .splitext (name )
98+                 if  mod  in  seen :
99+                     continue 
100+                 if  ext  in  ['.pyi' , '.py' ]:
101+                     if  match (args , full ):
102+                         seen .add (mod )
103+                         files .append (full )
104+                 elif  (os .path .isfile (os .path .join (full , '__init__.pyi' )) or 
105+                       os .path .isfile (os .path .join (full , '__init__.py' ))):
106+                     for  r , ds , fs  in  os .walk (full ):
107+                         ds .sort ()
108+                         fs .sort ()
109+                         for  f  in  fs :
110+                             m , x  =  os .path .splitext (f )
111+                             if  x  in  ['.pyi' , '.py' ]:
112+                                 fn  =  os .path .join (r , f )
113+                                 if  match (args , fn ):
114+                                     seen .add (mod )
115+                                     files .append (fn )
80116        if  files :
117+             runs  +=  1 
118+             flags  =  ['--python-version' , '%d.%d'  %  (major , minor )]
81119            sys .argv  =  ['mypy' ] +  flags  +  files 
82120            if  args .verbose :
83-                 print (' running' ' ' .join (sys .argv ))
121+                 print (" running" ' ' .join (sys .argv ))
84122            else :
85-                 print (' running mypy' ' ' .join (flags ), ' # with' len (files ), ' files' 
123+                 print (" running mypy" ' ' .join (flags ), " # with" len (files ), " files" 
86124            try :
87125                if  not  args .dry_run :
88126                    mypy_main ('' )
89127            except  SystemExit  as  err :
90128                code  =  max (code , err .code )
91129    if  code :
92-         print (' --- exit status' code , ' ---' 
130+         print (" --- exit status" code , " ---" 
93131        sys .exit (code )
132+     if  not  runs :
133+         print ("--- nothing to do; exit 1 ---" )
134+         sys .exit (1 )
94135
95136
96137if  __name__  ==  '__main__' :
0 commit comments