)]}'
{"refstack/api/controllers/subunit_data.py":[{"author":{"_account_id":7822,"name":"Chris Hoge (not involved in upstream development)","email":"chris@hogepodge.com","username":"hogepodge"},"change_message_id":"8ecd99d02ada4e50e7685fd5949e93eccd633682","unresolved":false,"context_lines":[{"line_number":41,"context_line":"        Session \u003d sessionmaker(bind\u003dengine)"},{"line_number":42,"context_line":"        self.session \u003d Session()"},{"line_number":43,"context_line":""},{"line_number":44,"context_line":"    def post(self, subunit_file):"},{"line_number":45,"context_line":"        \"\"\"Upload new subunit data.\"\"\""},{"line_number":46,"context_line":"        test_result_id \u003d None"},{"line_number":47,"context_line":"        subunit_uuid \u003d None"}],"source_content_type":"text/x-python","patch_set":3,"id":"7f96bb07_e88f5127","line":44,"updated":"2018-01-18 04:08:26.000000000","message":"This is a great start. One thing to think about is that we need the auth validation for posting subunit as we do json results.\n\nhttps://github.com/openstack/refstack/blob/master/refstack/api/controllers/results.py#L177","commit_id":"e053e5dbf3954251d9550594401905416c3e12fe"},{"author":{"_account_id":7822,"name":"Chris Hoge (not involved in upstream development)","email":"chris@hogepodge.com","username":"hogepodge"},"change_message_id":"f3ddd23d2498ae68bcdd70bd745d413fb187f159","unresolved":false,"context_lines":[{"line_number":77,"context_line":"    def post(self, subunit_file):"},{"line_number":78,"context_line":"        \"\"\"Upload new subunit data.\"\"\""},{"line_number":79,"context_line":"        test_id \u003d None"},{"line_number":80,"context_line":"        subunit_uuid \u003d None"},{"line_number":81,"context_line":"        # convert subunit file"},{"line_number":82,"context_line":"        v2_subunit_file \u003d api_utils.subunit_v1_to_v2(subunit_file)"},{"line_number":83,"context_line":"        # generate test result from subunit data"},{"line_number":84,"context_line":"        test_results \u003d \\"},{"line_number":85,"context_line":"            api_utils.subunit_to_test_result(subunit_file)"},{"line_number":86,"context_line":"        try:"},{"line_number":87,"context_line":"            test_id \u003d db.store_results(test_results)"},{"line_number":88,"context_line":"            self.session.begin_nested()"}],"source_content_type":"text/x-python","patch_set":5,"id":"5f93b717_6926d96a","line":85,"range":{"start_line":80,"start_character":0,"end_line":85,"end_character":58},"updated":"2018-02-01 23:04:34.000000000","message":"Worthwhile to apply DRY here and (see below)","commit_id":"8da148251e326adec31df50db1518c6d5e594f98"},{"author":{"_account_id":24728,"name":"megan guiney","email":"meganmguiney@gmail.com","username":"mguiney"},"change_message_id":"a810ab940b31c17ebfdcdf78da71f1a710f04b60","unresolved":false,"context_lines":[{"line_number":77,"context_line":"    def post(self, subunit_file):"},{"line_number":78,"context_line":"        \"\"\"Upload new subunit data.\"\"\""},{"line_number":79,"context_line":"        test_id \u003d None"},{"line_number":80,"context_line":"        subunit_uuid \u003d None"},{"line_number":81,"context_line":"        # convert subunit file"},{"line_number":82,"context_line":"        v2_subunit_file \u003d api_utils.subunit_v1_to_v2(subunit_file)"},{"line_number":83,"context_line":"        # generate test result from subunit data"},{"line_number":84,"context_line":"        test_results \u003d \\"},{"line_number":85,"context_line":"            api_utils.subunit_to_test_result(subunit_file)"},{"line_number":86,"context_line":"        try:"},{"line_number":87,"context_line":"            test_id \u003d db.store_results(test_results)"},{"line_number":88,"context_line":"            self.session.begin_nested()"}],"source_content_type":"text/x-python","patch_set":5,"id":"5f93b717_ef5c0e4c","line":85,"range":{"start_line":80,"start_character":0,"end_line":85,"end_character":58},"in_reply_to":"5f93b717_6926d96a","updated":"2018-02-03 06:54:09.000000000","message":"Done","commit_id":"8da148251e326adec31df50db1518c6d5e594f98"},{"author":{"_account_id":7822,"name":"Chris Hoge (not involved in upstream development)","email":"chris@hogepodge.com","username":"hogepodge"},"change_message_id":"f3ddd23d2498ae68bcdd70bd745d413fb187f159","unresolved":false,"context_lines":[{"line_number":108,"context_line":""},{"line_number":109,"context_line":"    def put(self, subunit_file):"},{"line_number":110,"context_line":"        \"\"\"Link new subunit data to an existing test result.\"\"\""},{"line_number":111,"context_line":"        subunit_uuid \u003d None"},{"line_number":112,"context_line":"        # convert subunit file to subunit v2"},{"line_number":113,"context_line":"        v2_subunit_file \u003d api_utils.subunit_v1_to_v2(subunit_file)"},{"line_number":114,"context_line":"        # generate test result"},{"line_number":115,"context_line":"        test_result \u003d api_utils.subunit_to_test_result(v2_subunit_file)"},{"line_number":116,"context_line":"        # search for a matching test result"},{"line_number":117,"context_line":"        test_match_id \u003d self._get_test_match(test_result,"},{"line_number":118,"context_line":"                                             self.session)"}],"source_content_type":"text/x-python","patch_set":5,"id":"5f93b717_2924515d","line":115,"range":{"start_line":111,"start_character":1,"end_line":115,"end_character":71},"updated":"2018-02-01 23:04:34.000000000","message":"and here","commit_id":"8da148251e326adec31df50db1518c6d5e594f98"},{"author":{"_account_id":24728,"name":"megan guiney","email":"meganmguiney@gmail.com","username":"mguiney"},"change_message_id":"a810ab940b31c17ebfdcdf78da71f1a710f04b60","unresolved":false,"context_lines":[{"line_number":108,"context_line":""},{"line_number":109,"context_line":"    def put(self, subunit_file):"},{"line_number":110,"context_line":"        \"\"\"Link new subunit data to an existing test result.\"\"\""},{"line_number":111,"context_line":"        subunit_uuid \u003d None"},{"line_number":112,"context_line":"        # convert subunit file to subunit v2"},{"line_number":113,"context_line":"        v2_subunit_file \u003d api_utils.subunit_v1_to_v2(subunit_file)"},{"line_number":114,"context_line":"        # generate test result"},{"line_number":115,"context_line":"        test_result \u003d api_utils.subunit_to_test_result(v2_subunit_file)"},{"line_number":116,"context_line":"        # search for a matching test result"},{"line_number":117,"context_line":"        test_match_id \u003d self._get_test_match(test_result,"},{"line_number":118,"context_line":"                                             self.session)"}],"source_content_type":"text/x-python","patch_set":5,"id":"5f93b717_8f578a26","line":115,"range":{"start_line":111,"start_character":1,"end_line":115,"end_character":71},"in_reply_to":"5f93b717_2924515d","updated":"2018-02-03 06:54:09.000000000","message":"Done","commit_id":"8da148251e326adec31df50db1518c6d5e594f98"},{"author":{"_account_id":7822,"name":"Chris Hoge (not involved in upstream development)","email":"chris@hogepodge.com","username":"hogepodge"},"change_message_id":"e15a7522cfdecec760ec1d871a970908db258b81","unresolved":false,"context_lines":[{"line_number":122,"context_line":""},{"line_number":123,"context_line":"    @pecan.expose(\u0027json\u0027)"},{"line_number":124,"context_line":"    @api_utils.check_permissions(level\u003dconst.ROLE_OWNER)"},{"line_number":125,"context_line":"    def put(self, subunit_file):"},{"line_number":126,"context_line":"        \"\"\"Handler for adding subunit data for an existing result.\"\"\""},{"line_number":127,"context_line":"        # generate test result"},{"line_number":128,"context_line":"        subunit_data, start_time, end_time \u003d \\"}],"source_content_type":"text/x-python","patch_set":8,"id":"3fa0c359_1331db5a","line":125,"updated":"2018-02-20 22:19:25.000000000","message":"Are you checking to see if the data uploaded in the subunit data produces results that match the existing results? We should reject anything where a new results.json generated from the subunit data does not match the preexisting json results.","commit_id":"fbf15d7261aa391f7c695bdf1d3b25a18e740b9e"},{"author":{"_account_id":7822,"name":"Chris Hoge (not involved in upstream development)","email":"chris@hogepodge.com","username":"hogepodge"},"change_message_id":"257113cc1c49b08850d587a7f43cbb1fcfd8a747","unresolved":false,"context_lines":[{"line_number":42,"context_line":"    session \u003d None"},{"line_number":43,"context_line":"    results_api \u003d None"},{"line_number":44,"context_line":""},{"line_number":45,"context_line":"    def _get_db_details(self):"},{"line_number":46,"context_line":"        db_uri \u003d CONF.database.connection"},{"line_number":47,"context_line":"        if not self.session:"},{"line_number":48,"context_line":"            engine \u003d create_engine(db_uri)"}],"source_content_type":"text/x-python","patch_set":21,"id":"5f7c97a3_74561d2d","line":45,"range":{"start_line":45,"start_character":0,"end_line":45,"end_character":30},"updated":"2018-07-03 06:26:16.000000000","message":"a more descriptive name might be _initialize_db_session","commit_id":"1195b7b5229a8f6a59296ba929769cf2b1ec13fc"},{"author":{"_account_id":24728,"name":"megan guiney","email":"meganmguiney@gmail.com","username":"mguiney"},"change_message_id":"95e3eea28ab6ef06291ba3e4e5e1d7eee464e795","unresolved":false,"context_lines":[{"line_number":42,"context_line":"    session \u003d None"},{"line_number":43,"context_line":"    results_api \u003d None"},{"line_number":44,"context_line":""},{"line_number":45,"context_line":"    def _get_db_details(self):"},{"line_number":46,"context_line":"        db_uri \u003d CONF.database.connection"},{"line_number":47,"context_line":"        if not self.session:"},{"line_number":48,"context_line":"            engine \u003d create_engine(db_uri)"}],"source_content_type":"text/x-python","patch_set":21,"id":"5f7c97a3_e830fba0","line":45,"range":{"start_line":45,"start_character":0,"end_line":45,"end_character":30},"in_reply_to":"5f7c97a3_74561d2d","updated":"2018-07-03 21:08:02.000000000","message":"Done","commit_id":"1195b7b5229a8f6a59296ba929769cf2b1ec13fc"},{"author":{"_account_id":7822,"name":"Chris Hoge (not involved in upstream development)","email":"chris@hogepodge.com","username":"hogepodge"},"change_message_id":"257113cc1c49b08850d587a7f43cbb1fcfd8a747","unresolved":false,"context_lines":[{"line_number":55,"context_line":"    def _get_test_match(self, subunit_result):"},{"line_number":56,"context_line":"        duration \u003d subunit_result[\u0027duration_seconds\u0027]"},{"line_number":57,"context_line":"        # search for matching db entry"},{"line_number":58,"context_line":"        test_object \u003d self.session.query(models.Test). \\"},{"line_number":59,"context_line":"            order_by(\u0027created_at\u0027). \\"},{"line_number":60,"context_line":"            filter_by(duration_seconds\u003dint(round(duration))).first()"},{"line_number":61,"context_line":"        # get passed tests using the result id"},{"line_number":62,"context_line":"        if test_object:"},{"line_number":63,"context_line":"            test_object \u003d test_object.__dict__"}],"source_content_type":"text/x-python","patch_set":21,"id":"5f7c97a3_544c419c","line":60,"range":{"start_line":58,"start_character":0,"end_line":60,"end_character":68},"updated":"2018-07-03 06:26:16.000000000","message":"Why are we only taking the first of what could be many different candidates?","commit_id":"1195b7b5229a8f6a59296ba929769cf2b1ec13fc"},{"author":{"_account_id":7822,"name":"Chris Hoge (not involved in upstream development)","email":"chris@hogepodge.com","username":"hogepodge"},"change_message_id":"257113cc1c49b08850d587a7f43cbb1fcfd8a747","unresolved":false,"context_lines":[{"line_number":160,"context_line":"                    \"result-id\": test_match_id"},{"line_number":161,"context_line":"                }"},{"line_number":162,"context_line":"                pecan.response.status \u003d 201"},{"line_number":163,"context_line":"                return put_results"},{"line_number":164,"context_line":"            except Exception as err:"},{"line_number":165,"context_line":"                if self.subunit_uuid:"},{"line_number":166,"context_line":"                    self.session.rollback()"}],"source_content_type":"text/x-python","patch_set":21,"id":"5f7c97a3_4f84a85d","line":163,"range":{"start_line":163,"start_character":16,"end_line":163,"end_character":34},"updated":"2018-07-03 06:26:16.000000000","message":"I\u0027m not a fan of the in-function return statement if it can be avoided. We should expect PUT to be idempotent (although we\u0027re going to get back different codes based on the context), and we can reduce the bug surface area by storing the return values in temporary variables then returning them at the end of the method.","commit_id":"1195b7b5229a8f6a59296ba929769cf2b1ec13fc"},{"author":{"_account_id":7822,"name":"Chris Hoge (not involved in upstream development)","email":"chris@hogepodge.com","username":"hogepodge"},"change_message_id":"257113cc1c49b08850d587a7f43cbb1fcfd8a747","unresolved":false,"context_lines":[{"line_number":167,"context_line":"                pecan.response.status \u003d 400"},{"line_number":168,"context_line":"                pecan.response.text \u003d err"},{"line_number":169,"context_line":"        else:"},{"line_number":170,"context_line":"            pecan.response.status \u003d 202"},{"line_number":171,"context_line":"            existing_matches \u003d {"},{"line_number":172,"context_line":"                \u0027subunit-uuid\u0027: subunit_match_id,"},{"line_number":173,"context_line":"                \u0027result-id\u0027: test_match_id"}],"source_content_type":"text/x-python","patch_set":21,"id":"5f7c97a3_ef7a5475","line":170,"range":{"start_line":170,"start_character":12,"end_line":170,"end_character":39},"updated":"2018-07-03 06:26:16.000000000","message":"Why 202? I thought 200 was typically the right response. I should check the code to see what our style is (and we can be consistent) :-)","commit_id":"1195b7b5229a8f6a59296ba929769cf2b1ec13fc"}],"refstack/api/utils.py":[{"author":{"_account_id":5196,"name":"Matthew Treinish","email":"mtreinish@kortar.org","username":"treinish"},"change_message_id":"9f4e4d238570d3a24432096c4af670eb576b5b30","unresolved":false,"context_lines":[{"line_number":436,"context_line":"    v2_file \u003d tempfile.NamedTemporaryFile(delete\u003dFalse)"},{"line_number":437,"context_line":"    v2_filename \u003d v2_file.name"},{"line_number":438,"context_line":"    convert_cmd \u003d [\"subunit-1to2\", subunit_file]"},{"line_number":439,"context_line":"    convert_exec \u003d subprocess.Popen(convert_cmd, stdout\u003dsubprocess.PIPE)"},{"line_number":440,"context_line":"    v2_subunit \u003d convert_exec.communicate()[0]"},{"line_number":441,"context_line":"    v2_file.write(v2_subunit)"},{"line_number":442,"context_line":"    return v2_filename"}],"source_content_type":"text/x-python","patch_set":5,"id":"5f93b717_c043293e","line":439,"updated":"2018-02-02 20:00:34.000000000","message":"You should be able to avoid subprocessing out here, this is just calling python code and keep it all in memory too. Doing something like::\n\n  import io\n  from subunit import filters\n  from subunit import StreamResultToBytes\n  import testtools\n\n  v2_file \u003d io.BytesIO()\n  filters.run_tests_from_stream(\n      subunit_file,\n      testtools.ExtendedToStreamDecorator(\n          StreamResultToBytes(v2_file)))\n  v2_file.seek(0)\n  return v2_file","commit_id":"8da148251e326adec31df50db1518c6d5e594f98"},{"author":{"_account_id":24728,"name":"megan guiney","email":"meganmguiney@gmail.com","username":"mguiney"},"change_message_id":"b3ee4e4f0a6e833e143f1e33710c69c43205c300","unresolved":false,"context_lines":[{"line_number":436,"context_line":"    v2_file \u003d tempfile.NamedTemporaryFile(delete\u003dFalse)"},{"line_number":437,"context_line":"    v2_filename \u003d v2_file.name"},{"line_number":438,"context_line":"    convert_cmd \u003d [\"subunit-1to2\", subunit_file]"},{"line_number":439,"context_line":"    convert_exec \u003d subprocess.Popen(convert_cmd, stdout\u003dsubprocess.PIPE)"},{"line_number":440,"context_line":"    v2_subunit \u003d convert_exec.communicate()[0]"},{"line_number":441,"context_line":"    v2_file.write(v2_subunit)"},{"line_number":442,"context_line":"    return v2_filename"}],"source_content_type":"text/x-python","patch_set":5,"id":"3fa0c359_ae84835f","line":439,"in_reply_to":"5f93b717_c043293e","updated":"2018-03-08 07:32:25.000000000","message":"Done","commit_id":"8da148251e326adec31df50db1518c6d5e594f98"},{"author":{"_account_id":5196,"name":"Matthew Treinish","email":"mtreinish@kortar.org","username":"treinish"},"change_message_id":"9f4e4d238570d3a24432096c4af670eb576b5b30","unresolved":false,"context_lines":[{"line_number":451,"context_line":"               \"--database-connection\", db_uri,"},{"line_number":452,"context_line":"               \"--run_at\", str(start_time),"},{"line_number":453,"context_line":"               subunit_file]"},{"line_number":454,"context_line":"    subprocess.Popen(cli_cmd, stdout\u003dsubprocess.PIPE)"},{"line_number":455,"context_line":"    session.commit()"},{"line_number":456,"context_line":"    latest_run \u003d api.get_latest_run(session\u003dsession)"},{"line_number":457,"context_line":"    return latest_run.uuid"}],"source_content_type":"text/x-python","patch_set":5,"id":"5f93b717_e06bc5b3","line":454,"updated":"2018-02-02 20:00:34.000000000","message":"Again, this is also python code. You should be able to avoid shelling out to python again. This code is almost identical to: https://github.com/openstack-infra/puppet-subunit2sql/blob/master/files/subunit-gearman-worker.py#L81-L115\n\nDoing it all in python natively should also avoid you needing to play the games with session management you are here. Where you need to make sure you commit in both processes to ensure they get the same state when there are subsequent reads from the db. You can just use the same session for everything and then you don\u0027t have to worry.\n\nAlso if you want to avoid doing a second call here you could probably just implement a copy of (or propose a subunit2sql patch to make it return) subunit2sql.shell.process_results() here that will return the run uuid instead of the doing an additional query.","commit_id":"8da148251e326adec31df50db1518c6d5e594f98"},{"author":{"_account_id":5196,"name":"Matthew Treinish","email":"mtreinish@kortar.org","username":"treinish"},"change_message_id":"9f4e4d238570d3a24432096c4af670eb576b5b30","unresolved":false,"context_lines":[{"line_number":491,"context_line":"    start_time \u003d datetime.strptime(timestamps[0], \"%Y-%m-%d %H:%M:%S\")"},{"line_number":492,"context_line":"    end_time \u003d datetime.strptime(timestamps[-1], \"%Y-%m-%d %H:%M:%S\")"},{"line_number":493,"context_line":"    duration \u003d (end_time - start_time).total_seconds()"},{"line_number":494,"context_line":"    return start_time, end_time, int(duration)"},{"line_number":495,"context_line":""},{"line_number":496,"context_line":""},{"line_number":497,"context_line":"def get_tests(subunit_file):"}],"source_content_type":"text/x-python","patch_set":5,"id":"5f93b717_e0de656b","line":494,"updated":"2018-02-02 20:00:34.000000000","message":"I\u0027m not sure the exact call path for this (I might not have gotten far enough in the patch, or just missed where subunit_to_test_results() is called) but this function is basically already implemented in the parse path for subunit2sql: https://github.com/openstack-infra/subunit2sql/blob/master/subunit2sql/read_subunit.py#L144-L152","commit_id":"8da148251e326adec31df50db1518c6d5e594f98"},{"author":{"_account_id":5196,"name":"Matthew Treinish","email":"mtreinish@kortar.org","username":"treinish"},"change_message_id":"2ad49975ffc601e57e356d883fe83d8408af5261","unresolved":false,"context_lines":[{"line_number":491,"context_line":"    start_time \u003d datetime.strptime(timestamps[0], \"%Y-%m-%d %H:%M:%S\")"},{"line_number":492,"context_line":"    end_time \u003d datetime.strptime(timestamps[-1], \"%Y-%m-%d %H:%M:%S\")"},{"line_number":493,"context_line":"    duration \u003d (end_time - start_time).total_seconds()"},{"line_number":494,"context_line":"    return start_time, end_time, int(duration)"},{"line_number":495,"context_line":""},{"line_number":496,"context_line":""},{"line_number":497,"context_line":"def get_tests(subunit_file):"}],"source_content_type":"text/x-python","patch_set":5,"id":"5f93b717_c04d693b","line":494,"in_reply_to":"5f93b717_e0de656b","updated":"2018-02-02 20:03:35.000000000","message":"Oops, didn\u0027t finish my though here. But depending on where this is used you can just leverage the use wall time flag with subunit2sql and then you\u0027ll store this run_time in the database. So you can just get that directly with a simple query instead of having to calculate it out of band like this.","commit_id":"8da148251e326adec31df50db1518c6d5e594f98"}]}
