├── README.md ├── chainerrl-hint.txt ├── LICENSE └── parallel-pytest.py /README.md: -------------------------------------------------------------------------------- 1 | # public -------------------------------------------------------------------------------- /chainerrl-hint.txt: -------------------------------------------------------------------------------- 1 | tests/agents_tests/test_ppo.py 2 | tests/agents_tests/test_trpo.py 3 | tests/agents_tests/test_acer.py 4 | tests/policies_tests/test_deterministic_policy.py 5 | tests/test_ale.py 6 | tests/agents_tests/test_a2c.py 7 | tests/agents_tests/test_pcl.py 8 | tests/q_functions_tests/test_state_action_q_function.py 9 | tests/explorers_tests/test_boltzmann.py 10 | tests/test_distribution.py 11 | tests/agents_tests/test_a3c.py 12 | tests/agents_tests/test_dqn.py 13 | tests/misc_tests/test_prioritized.py 14 | tests/links_tests/test_mlp_bn.py 15 | tests/agents_tests/test_iqn.py 16 | tests/agents_tests/test_dpp.py 17 | tests/functions_tests/test_mellowmax.py 18 | tests/agents_tests/test_nsq.py 19 | tests/agents_tests/test_reinforce.py 20 | tests/envs_tests/test_vector_envs.py 21 | tests/agents_tests/test_pal.py 22 | tests/agents_tests/test_double_dqn.py 23 | tests/agents_tests/test_ddpg.py 24 | tests/agents_tests/test_al.py 25 | tests/experiments_tests/test_prepare_output_dir.py 26 | tests/agents_tests/test_residual_dqn.py 27 | tests/agents_tests/test_categorical_dqn.py 28 | tests/agents_tests/test_agents.py 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Kentaro IMAJO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /parallel-pytest.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import re 3 | import subprocess 4 | import threading 5 | import fnmatch 6 | import os 7 | import sys 8 | import six.moves.queue as queue 9 | 10 | 11 | class Collector(threading.Thread): 12 | def __init__(self): 13 | self.__output = queue.Queue() 14 | self.is_failure = False 15 | super(Collector, self).__init__() 16 | self.start() 17 | 18 | def run(self): 19 | while True: 20 | line = self.__output.get() 21 | if line is None: 22 | break 23 | print(line) 24 | sys.stdout.flush() 25 | 26 | def put(self, line, is_failure): 27 | self.__output.put(line) 28 | if is_failure: 29 | self.is_failure = True 30 | 31 | 32 | class Executor(threading.Thread): 33 | def __init__(self, queue, collector): 34 | self.__queue = queue 35 | self.__collector = collector 36 | super(Executor, self).__init__() 37 | self.start() 38 | 39 | def run(self): 40 | try: 41 | while True: 42 | item = self.__queue.get(block=False) 43 | p = subprocess.Popen( 44 | item["command"], shell=True, 45 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) 46 | stdout, stderr = p.communicate() 47 | line = stdout.strip().rsplit(b"\n", 1)[-1] 48 | line = re.sub(b"^=*", b"", line) 49 | line = re.sub(b"=*$", b"", line) 50 | line = line.strip() 51 | # For pytest. 52 | if re.match( 53 | b'^\\d+ deselected in \\d+(\\.\\d+)? seconds$', line): 54 | p.returncode = 0 55 | # For pytest-xdist. 56 | if re.match( 57 | b'^no tests ran in \\d+(\\.\\d+)? seconds$', line): 58 | p.returncode = 0 59 | extra_info = b"" 60 | if p.returncode != 0: 61 | extra_info = b"\n"+stdout+stderr 62 | self.__collector.put("[%s] %s (%s)%s" % ( 63 | "SUCCESS" if p.returncode == 0 else "FAILED", 64 | item["file"], line.decode("utf-8"), 65 | extra_info.decode("utf-8")), 66 | p.returncode != 0) 67 | except queue.Empty: 68 | pass 69 | 70 | 71 | def recursive_glob(hint, directory): 72 | files = [] 73 | seen = {} 74 | if hint != "": 75 | with open(hint) as f: 76 | for file in f: 77 | file = file.strip() 78 | if os.path.exists(file): 79 | files.append(file) 80 | seen[file] = True 81 | for root, dirs, fs in os.walk(directory): 82 | for f in fnmatch.filter(fs, 'test_*.py'): 83 | file = os.path.join(root, f) 84 | if file not in seen: 85 | files.append(os.path.join(root, f)) 86 | return files 87 | 88 | 89 | def main(args): 90 | tests = queue.Queue() 91 | for f in recursive_glob(args.hint, args.directory): 92 | tests.put({ 93 | "file": f, 94 | "command": args.pytest + " " + f, 95 | }) 96 | collector = Collector() 97 | threads = [] 98 | for i in range(args.threads): 99 | threads.append(Executor(tests, collector)) 100 | for thread in threads: 101 | thread.join() 102 | collector.put(None, False) 103 | collector.join() 104 | if collector.is_failure: 105 | print("test failed") 106 | exit(1) 107 | else: 108 | print("test succeeded") 109 | exit(0) 110 | 111 | 112 | parser = argparse.ArgumentParser() 113 | parser.add_argument("--hint", default="") 114 | parser.add_argument("--directory", default=".") 115 | parser.add_argument("--pytest", default="pytest -m 'not slow and not gpu'") 116 | parser.add_argument("--threads", type=int, default=8) 117 | main(parser.parse_args()) 118 | --------------------------------------------------------------------------------