Exemple #1
0
    def __init__(self,
                 authorization_key=None,
                 project_id=None,
                 clear_previous=False):
        '''
        :param str project_id:
            This is the id of the project (available in the Dashboard/Projects, next to the project
            name).

        :param str authorization_key:
            The key which is used to authorize with the backend (available in the Dashboard/User
            Settings).
        '''
        if authorization_key is None:
            try:
                authorization_key = os.environ['SPEEDTIN_AUTHORIZATION_KEY']
            except KeyError:
                raise AssertionError(
                    'The authorization key to commit the data was not passed nor is available in the SPEEDTIN_AUTHORIZATION_KEY environment variable.'
                )

        if project_id is None:
            try:
                project_id = os.environ['SPEEDTIN_PROJECT_ID']
            except KeyError:
                raise AssertionError(
                    'The project_id to commit the data was not passed nor is available in the SPEEDTIN_PROJECT_ID environment variable.'
                )

        self.authorization_key = authorization_key
        self.project_id = project_id

        self.base_url = 'https://www.speedtin.com'
        self.post = requests.post
        self.get = requests.get
        self._local_cache = LocalCache(
            os.path.join(self._data_dir(), str(project_id)))

        if clear_previous:
            self._local_cache.clear('measurement')
        else:
            # Print that we have remaining data from a previous call
            found = []
            with self._local_cache.load('measurement') as measurement_data:
                for handle in measurement_data:
                    found.append(handle.data)

            if found:
                sys.stderr.write(
                    'Warning: PySpeedTinApi:\nIn a previous call the following measurements where not properly saved:\n'
                )
                for data in found:
                    sys.stderr.write(str(data))
                    sys.stderr.write('\n')
                sys.stderr.write(
                    '\nWhen committing, those values will also be saved...\n')
                sys.stderr.write(
                    'To prevent this from happening, pass "clear_previous=True" in the \n'
                    'PySpeedTinApi constructor or manually erase the contents at:\n%s\n\n'
                    % (self._data_dir()))
Exemple #2
0
    def __init__(self, authorization_key=None, project_id=None, clear_previous=False):
        '''
        :param str project_id:
            This is the id of the project (available in the Dashboard/Projects, next to the project
            name).

        :param str authorization_key:
            The key which is used to authorize with the backend (available in the Dashboard/User
            Settings).
        '''
        if authorization_key is None:
            try:
                authorization_key = os.environ['SPEEDTIN_AUTHORIZATION_KEY']
            except KeyError:
                raise AssertionError(
                    'The authorization key to commit the data was not passed nor is available in the SPEEDTIN_AUTHORIZATION_KEY environment variable.')

        if project_id is None:
            try:
                project_id = os.environ['SPEEDTIN_PROJECT_ID']
            except KeyError:
                raise AssertionError(
                    'The project_id to commit the data was not passed nor is available in the SPEEDTIN_PROJECT_ID environment variable.')

        self.authorization_key = authorization_key
        self.project_id = project_id



        self.base_url = 'https://www.speedtin.com'
        self.post = requests.post
        self.get = requests.get
        self._local_cache = LocalCache(os.path.join(self._data_dir(), str(project_id)))

        if clear_previous:
            self._local_cache.clear('measurement')
        else:
            # Print that we have remaining data from a previous call
            found = []
            with self._local_cache.load('measurement') as measurement_data:
                for handle in measurement_data:
                    found.append(handle.data)

            if found:
                sys.stderr.write('Warning: PySpeedTinApi:\nIn a previous call the following measurements where not properly saved:\n')
                for data in found:
                    sys.stderr.write(str(data))
                    sys.stderr.write('\n')
                sys.stderr.write('\nWhen committing, those values will also be saved...\n')
                sys.stderr.write(
                    'To prevent this from happening, pass "clear_previous=True" in the \n'
                    'PySpeedTinApi constructor or manually erase the contents at:\n%s\n\n' % (self._data_dir()))
def test_local_cache(tmpdir):
    print(tmpdir)
    local_cache = LocalCache(str(tmpdir))
    local_cache.add('benchmark', {'name': 'bench1'})
    local_cache.add('benchmark', {'name': 'bench1'}) # Ignore this one (don't add duplicate data)
    local_cache.add('benchmark', {'name': 'bench2'})
    
    data_found = []
    with local_cache.load('benchmark') as benchmark_data:
        for handle in benchmark_data:
            assert not handle.has_rest_data()
            data_found.append(handle.data)
            handle.set_rest_data('something')
            assert handle.has_rest_data()
            
    assert data_found == [{'name': 'bench1'}, {'name': 'bench2'}]
    
    data_found = []
    rest_data_found = []
    with local_cache.load('benchmark') as benchmark_data:
        for handle in benchmark_data:
            assert handle.has_rest_data()
            data_found.append(handle.data)
            rest_data_found.append(handle.rest_data)

    assert data_found == [{'name': 'bench1'}, {'name': 'bench2'}]
    assert rest_data_found == ['something', 'something']
    
    data_found = []
    with local_cache.load('benchmark') as benchmark_data:
        for i, handle in enumerate(benchmark_data):
            if i == 0:
                handle.remove()
            data_found.append(handle.data)
    assert data_found == [{'name': 'bench1'}, {'name': 'bench2'}]

                
    data_found = []
    with local_cache.load('benchmark') as benchmark_data:
        for i, handle in enumerate(benchmark_data):
            data_found.append(handle.data)
    assert data_found == [{'name': 'bench2'}]
Exemple #4
0
def test_local_cache(tmpdir):
    print(tmpdir)
    local_cache = LocalCache(str(tmpdir))
    local_cache.add('benchmark', {'name': 'bench1'})
    local_cache.add(
        'benchmark',
        {'name': 'bench1'})  # Ignore this one (don't add duplicate data)
    local_cache.add('benchmark', {'name': 'bench2'})

    data_found = []
    with local_cache.load('benchmark') as benchmark_data:
        for handle in benchmark_data:
            assert not handle.has_rest_data()
            data_found.append(handle.data)
            handle.set_rest_data('something')
            assert handle.has_rest_data()

    assert data_found == [{'name': 'bench1'}, {'name': 'bench2'}]

    data_found = []
    rest_data_found = []
    with local_cache.load('benchmark') as benchmark_data:
        for handle in benchmark_data:
            assert handle.has_rest_data()
            data_found.append(handle.data)
            rest_data_found.append(handle.rest_data)

    assert data_found == [{'name': 'bench1'}, {'name': 'bench2'}]
    assert rest_data_found == ['something', 'something']

    data_found = []
    with local_cache.load('benchmark') as benchmark_data:
        for i, handle in enumerate(benchmark_data):
            if i == 0:
                handle.remove()
            data_found.append(handle.data)
    assert data_found == [{'name': 'bench1'}, {'name': 'bench2'}]

    data_found = []
    with local_cache.load('benchmark') as benchmark_data:
        for i, handle in enumerate(benchmark_data):
            data_found.append(handle.data)
    assert data_found == [{'name': 'bench2'}]
Exemple #5
0
class PySpeedTinApi(object):
    '''
    This API caches things locally as much as possible and provides a way to create measurements
    based on the benchmark name (and not only the benchmark id, which is required by the REST API).

    It creates a local dict in the user settings dir to act as a local cache so that it doesn't
    need to query the server to get the benchmark id from the benchmark name.
    '''

    def __init__(self, authorization_key=None, project_id=None, clear_previous=False):
        '''
        :param str project_id:
            This is the id of the project (available in the Dashboard/Projects, next to the project
            name).

        :param str authorization_key:
            The key which is used to authorize with the backend (available in the Dashboard/User
            Settings).
        '''
        if authorization_key is None:
            try:
                authorization_key = os.environ['SPEEDTIN_AUTHORIZATION_KEY']
            except KeyError:
                raise AssertionError(
                    'The authorization key to commit the data was not passed nor is available in the SPEEDTIN_AUTHORIZATION_KEY environment variable.')

        if project_id is None:
            try:
                project_id = os.environ['SPEEDTIN_PROJECT_ID']
            except KeyError:
                raise AssertionError(
                    'The project_id to commit the data was not passed nor is available in the SPEEDTIN_PROJECT_ID environment variable.')

        self.authorization_key = authorization_key
        self.project_id = project_id



        self.base_url = 'https://www.speedtin.com'
        self.post = requests.post
        self.get = requests.get
        self._local_cache = LocalCache(os.path.join(self._data_dir(), str(project_id)))

        if clear_previous:
            self._local_cache.clear('measurement')
        else:
            # Print that we have remaining data from a previous call
            found = []
            with self._local_cache.load('measurement') as measurement_data:
                for handle in measurement_data:
                    found.append(handle.data)

            if found:
                sys.stderr.write('Warning: PySpeedTinApi:\nIn a previous call the following measurements where not properly saved:\n')
                for data in found:
                    sys.stderr.write(str(data))
                    sys.stderr.write('\n')
                sys.stderr.write('\nWhen committing, those values will also be saved...\n')
                sys.stderr.write(
                    'To prevent this from happening, pass "clear_previous=True" in the \n'
                    'PySpeedTinApi constructor or manually erase the contents at:\n%s\n\n' % (self._data_dir()))

    def date_to_str(self, date):
        return date.strftime('%Y-%m-%d %H:%M:%S.%f')

    def str_to_date(self, s):
        return datetime.datetime.strptime(s, '%Y-%m-%d %H:%M:%S.%f')

    def curr_date(self):
        return datetime.datetime.utcnow()

    def _data_dir(self):
        return os.path.join(os.path.expanduser('~'), '.speedtin')

    def commit(self):
        sys.stdout.write('Commit results...\n')
        assert not self.base_url.endswith('/'), 'The base url must not end with a slash.'
        self._commit_benchmarks()
        self._commit_measurements()

    def _commit_benchmarks(self):
        project_id = self.project_id
        authorization_key = self.authorization_key
        with self._local_cache.load('benchmark') as benchmark_data:
            for handle in benchmark_data:
                if not handle.has_rest_data():
                    data = handle.data
                    as_json = self.post_and_check_resut(
                        '%s/api/projects/%s/benchmarks' % (self.base_url, project_id),
                        json=data,
                        headers={'X-AuthToken': authorization_key},
                        msg='It was not possible to create the benchmark',
                        expected_status=201,
                    )
                    handle.set_rest_data(as_json)
                    sys.stdout.write('Saved benchmark: %s\n' % (as_json,))

    def _commit_measurements(self):
        project_id = self.project_id
        authorization_key = self.authorization_key

        with self._local_cache.load('benchmark') as benchmark_data:
            benchmark_name_to_id = {}
            for handle in benchmark_data:
                benchmark_name_to_id[int(handle.rest_data['id'])] = handle.data['name']

        with self._local_cache.load('measurement') as measurement_data:
            for handle in measurement_data:
                benchmark_id, json = handle.data

                try:
                    int(benchmark_id)
                except ValueError:
                    # The benchmark_id is actually the name of the benchmark, so, we have
                    # to get its id from the name.
                    try:
                        benchmark_id = benchmark_name_to_id[benchmark_id]
                    except KeyError:
                        # Ok, it's not there, try to get it from the REST API (and take the
                        # chance to update our local cache).
                        benchmarks_request = self.get('%s/api/projects/%s/benchmarks' % (
                            self.base_url,
                            project_id), headers={'X-AuthToken': authorization_key})
                        as_json = self.check_request_result(
                            benchmarks_request,
                            'Unable to get the benchmarks from the server',
                            expected_status=200,
                        )

                        for benchmark in as_json:
                            if benchmark['name'] not in benchmark_name_to_id:
                                benchmark_name_to_id[benchmark['name']] = int(benchmark['id'])
                                self._local_cache.add(
                                    'benchmark', {'name': benchmark['name']}, benchmark)
                        try:
                            benchmark_id = benchmark_name_to_id[benchmark_id]
                        except KeyError:
                            raise ValueError(
                                'Unable to find benchmark with the name: %s' % (benchmark_id))

                as_json = self.post_and_check_resut(
                    '%s/api/projects/%s/benchmarks/%s/measurements' % (
                        self.base_url, project_id, benchmark_id),
                    json=json,
                    headers={'X-AuthToken': authorization_key},
                    msg='It was not possible to create the measurement',
                    expected_status=201,
                )
                sys.stdout.write('Saved measurement: %s\n' % (as_json,))
                handle.remove()

    def post_and_check_resut(self, url, json, headers, msg, expected_status):
        r = self.post(url, json=json, headers=headers, allow_redirects=False)
        as_json = self.check_request_result(r, msg+' Url: %s, Json: %s' % (url, json), expected_status)
        return as_json

    def check_request_result(self, r, msg, expected_status=201):
        if r.status_code != expected_status:
            raise RuntimeError('%s. Expected status: %s != %s Msg: %s' % (msg, expected_status, r.status_code, r.text,))

        as_json = r.json()

        if 'error' in as_json:
            raise RuntimeError('%s. Msg: %s' % (msg, r.text,))
        return as_json

    def add_benchmark(
        self,
        name,
    ):
        '''
        Creates a benchmark with the given name.

        :param str name:
            The name of the benchmark to be created.
        '''
        if len(name) > 50:
            raise ValueError('The maximum benchmark name size is 50 chars. The one passed has: %s chars' % (len(name),))
        self._local_cache.add('benchmark', {'name': name})

    def add_measurement(
        self,
        benchmark_id,
        value,
        version='dev',
        released=False,
        branch='',
        os=sys.platform,

        commit_id='',
        commit_date='',
        machine_name='',
        tag1='',
        tag2='',
    ):
        '''
        :param str benchmark_id:
            This is the id of the benchmark.

        :param float value:
            The value of the measurement. The actual meaning depends on the unit which is set in the
            Benchmark (i.e.: it could be seconds, count, etc.)

        :param str version:
            A string which identifies the version.
            i.e.: 0.7.1, 2.7.9

        :param bool released:
            A boolean that identifies if this is a version which was released to your users.
            Automated regression reports from SpeedTin may give warnings for slower measurements
            considering the run against the latest released version.

        :param branch:
            This is the branch of the code used to do the measurement.
            i.e.: master, development, ...

        :param str os:
            The os in which the benchmark was run (Windows, Linux, MacOs)

        :param commit_id:
            A hash for the commit for which this measurement was created.

        :param str commit_date:
            The date of the commit for this measurement.
            The date format is an utc date as: 2015-10-11 15:30:39.000

        :param str machine_name:
            The name of the machine in which this measurement was run.

        :param str tag1:
            Any value you feel it's important to tag this measurement.

        :param tag2:
            Any value you feel it's important to tag this measurement.
        '''
        if not machine_name:
            import socket
            machine_name = socket.gethostname()

        if not machine_name:
            raise RuntimeError('Please specify machine name.')
        json = {
            'value': value,
            'version': version,
            'released': released,
            'branch': branch,
            'os': os,
            'commit_id': commit_id,
            'commit_date': commit_date,
            'machine_name': machine_name,
            'tag1': tag1,
            'tag2': tag2,
        }
        self._local_cache.add(
            'measurement',
            (
                benchmark_id,
                json
            )
        )

    def run_and_get_output(self, *popenargs, **kwargs):
        '''
        Run command with arguments and return its output.
        '''
        process = subprocess.Popen(*popenargs, stdout=subprocess.PIPE, **kwargs)
        try:
            output, unused_err = process.communicate()
        except:
            process.kill()
            process.wait()
            raise
        retcode = process.poll()
        if retcode:
            raise Exception('Process exited with value: %s. Args: %s. Output: %s' % (retcode, process.args, output))
        return output

    def git_commit_id_branch_and_date_from_path(self, repo_path):
        '''
        :param repo_path:

        :return tuple(str, str):
            Returns a tuple with the commit_id and the commit_date obtained from the given path
            (the repo_path must the one containing the .git folder or a sub-directory).
        '''
        if not os.path.exists(repo_path):
            raise OSError('The path: %s does not exist.' % (repo_path,))

        while not os.path.exists(os.path.join(repo_path, '.git')):
            initial = repo_path
            repo_path = os.path.dirname(repo_path)
            if initial == repo_path or not repo_path:
                raise OSError('The path: %s does not seem to be a git-managed path.' % (repo_path,))

        commit_id = self.run_and_get_output('git rev-parse HEAD'.split()).strip().decode('utf-8')
        branch = self.run_and_get_output(
            'git rev-parse --abbrev-ref HEAD'.split()).strip().decode('utf-8')
        commit_date = self.run_and_get_output(
            ['git', 'show', '-s', '--format=%ct', commit_id]).strip().decode('utf-8')

        commit_date = datetime.datetime.utcfromtimestamp(int(commit_date))
        return commit_id, branch, commit_date
Exemple #6
0
class PySpeedTinApi(object):
    '''
    This API caches things locally as much as possible and provides a way to create measurements
    based on the benchmark name (and not only the benchmark id, which is required by the REST API).

    It creates a local dict in the user settings dir to act as a local cache so that it doesn't
    need to query the server to get the benchmark id from the benchmark name.
    '''
    def __init__(self,
                 authorization_key=None,
                 project_id=None,
                 clear_previous=False):
        '''
        :param str project_id:
            This is the id of the project (available in the Dashboard/Projects, next to the project
            name).

        :param str authorization_key:
            The key which is used to authorize with the backend (available in the Dashboard/User
            Settings).
        '''
        if authorization_key is None:
            try:
                authorization_key = os.environ['SPEEDTIN_AUTHORIZATION_KEY']
            except KeyError:
                raise AssertionError(
                    'The authorization key to commit the data was not passed nor is available in the SPEEDTIN_AUTHORIZATION_KEY environment variable.'
                )

        if project_id is None:
            try:
                project_id = os.environ['SPEEDTIN_PROJECT_ID']
            except KeyError:
                raise AssertionError(
                    'The project_id to commit the data was not passed nor is available in the SPEEDTIN_PROJECT_ID environment variable.'
                )

        self.authorization_key = authorization_key
        self.project_id = project_id

        self.base_url = 'https://www.speedtin.com'
        self.post = requests.post
        self.get = requests.get
        self._local_cache = LocalCache(
            os.path.join(self._data_dir(), str(project_id)))

        if clear_previous:
            self._local_cache.clear('measurement')
        else:
            # Print that we have remaining data from a previous call
            found = []
            with self._local_cache.load('measurement') as measurement_data:
                for handle in measurement_data:
                    found.append(handle.data)

            if found:
                sys.stderr.write(
                    'Warning: PySpeedTinApi:\nIn a previous call the following measurements where not properly saved:\n'
                )
                for data in found:
                    sys.stderr.write(str(data))
                    sys.stderr.write('\n')
                sys.stderr.write(
                    '\nWhen committing, those values will also be saved...\n')
                sys.stderr.write(
                    'To prevent this from happening, pass "clear_previous=True" in the \n'
                    'PySpeedTinApi constructor or manually erase the contents at:\n%s\n\n'
                    % (self._data_dir()))

    def date_to_str(self, date):
        return date.strftime('%Y-%m-%d %H:%M:%S.%f')

    def str_to_date(self, s):
        return datetime.datetime.strptime(s, '%Y-%m-%d %H:%M:%S.%f')

    def curr_date(self):
        return datetime.datetime.utcnow()

    def _data_dir(self):
        return os.path.join(os.path.expanduser('~'), '.speedtin')

    def commit(self):
        sys.stdout.write('Commit results...\n')
        assert not self.base_url.endswith(
            '/'), 'The base url must not end with a slash.'
        self._commit_benchmarks()
        self._commit_measurements()

    def _commit_benchmarks(self):
        project_id = self.project_id
        authorization_key = self.authorization_key
        with self._local_cache.load('benchmark') as benchmark_data:
            for handle in benchmark_data:
                if not handle.has_rest_data():
                    data = handle.data
                    as_json = self.post_and_check_resut(
                        '%s/api/projects/%s/benchmarks' %
                        (self.base_url, project_id),
                        json=data,
                        headers={'X-AuthToken': authorization_key},
                        msg='It was not possible to create the benchmark',
                        expected_status=201,
                    )
                    handle.set_rest_data(as_json)
                    sys.stdout.write('Saved benchmark: %s\n' % (as_json, ))

    def _commit_measurements(self):
        project_id = self.project_id
        authorization_key = self.authorization_key

        with self._local_cache.load('benchmark') as benchmark_data:
            benchmark_name_to_id = {}
            for handle in benchmark_data:
                benchmark_name_to_id[int(
                    handle.rest_data['id'])] = handle.data['name']

        with self._local_cache.load('measurement') as measurement_data:
            for handle in measurement_data:
                benchmark_id, json = handle.data

                try:
                    int(benchmark_id)
                except ValueError:
                    # The benchmark_id is actually the name of the benchmark, so, we have
                    # to get its id from the name.
                    try:
                        benchmark_id = benchmark_name_to_id[benchmark_id]
                    except KeyError:
                        # Ok, it's not there, try to get it from the REST API (and take the
                        # chance to update our local cache).
                        benchmarks_request = self.get(
                            '%s/api/projects/%s/benchmarks' %
                            (self.base_url, project_id),
                            headers={'X-AuthToken': authorization_key})
                        as_json = self.check_request_result(
                            benchmarks_request,
                            'Unable to get the benchmarks from the server',
                            expected_status=200,
                        )

                        for benchmark in as_json:
                            if benchmark['name'] not in benchmark_name_to_id:
                                benchmark_name_to_id[benchmark['name']] = int(
                                    benchmark['id'])
                                self._local_cache.add(
                                    'benchmark', {'name': benchmark['name']},
                                    benchmark)
                        try:
                            benchmark_id = benchmark_name_to_id[benchmark_id]
                        except KeyError:
                            raise ValueError(
                                'Unable to find benchmark with the name: %s' %
                                (benchmark_id))

                as_json = self.post_and_check_resut(
                    '%s/api/projects/%s/benchmarks/%s/measurements' %
                    (self.base_url, project_id, benchmark_id),
                    json=json,
                    headers={'X-AuthToken': authorization_key},
                    msg='It was not possible to create the measurement',
                    expected_status=201,
                )
                sys.stdout.write('Saved measurement: %s\n' % (as_json, ))
                handle.remove()

    def post_and_check_resut(self, url, json, headers, msg, expected_status):
        r = self.post(url, json=json, headers=headers, allow_redirects=False)
        as_json = self.check_request_result(
            r, msg + ' Url: %s, Json: %s' % (url, json), expected_status)
        return as_json

    def check_request_result(self, r, msg, expected_status=201):
        if r.status_code != expected_status:
            raise RuntimeError('%s. Expected status: %s != %s Msg: %s' % (
                msg,
                expected_status,
                r.status_code,
                r.text,
            ))

        as_json = r.json()

        if 'error' in as_json:
            raise RuntimeError('%s. Msg: %s' % (
                msg,
                r.text,
            ))
        return as_json

    def add_benchmark(
        self,
        name,
    ):
        '''
        Creates a benchmark with the given name.

        :param str name:
            The name of the benchmark to be created.
        '''
        if len(name) > 50:
            raise ValueError(
                'The maximum benchmark name size is 50 chars. The one passed has: %s chars'
                % (len(name), ))
        self._local_cache.add('benchmark', {'name': name})

    def add_measurement(
        self,
        benchmark_id,
        value,
        version='dev',
        released=False,
        branch='',
        os=sys.platform,
        commit_id='',
        commit_date='',
        machine_name='',
        tag1='',
        tag2='',
    ):
        '''
        :param str benchmark_id:
            This is the id of the benchmark.

        :param float value:
            The value of the measurement. The actual meaning depends on the unit which is set in the
            Benchmark (i.e.: it could be seconds, count, etc.)

        :param str version:
            A string which identifies the version.
            i.e.: 0.7.1, 2.7.9

        :param bool released:
            A boolean that identifies if this is a version which was released to your users.
            Automated regression reports from SpeedTin may give warnings for slower measurements
            considering the run against the latest released version.

        :param branch:
            This is the branch of the code used to do the measurement.
            i.e.: master, development, ...

        :param str os:
            The os in which the benchmark was run (Windows, Linux, MacOs)

        :param commit_id:
            A hash for the commit for which this measurement was created.

        :param str commit_date:
            The date of the commit for this measurement.
            The date format is an utc date as: 2015-10-11 15:30:39.000

        :param str machine_name:
            The name of the machine in which this measurement was run.

        :param str tag1:
            Any value you feel it's important to tag this measurement.

        :param tag2:
            Any value you feel it's important to tag this measurement.
        '''
        if not machine_name:
            import socket
            machine_name = socket.gethostname()

        if not machine_name:
            raise RuntimeError('Please specify machine name.')
        json = {
            'value': value,
            'version': version,
            'released': released,
            'branch': branch,
            'os': os,
            'commit_id': commit_id,
            'commit_date': commit_date,
            'machine_name': machine_name,
            'tag1': tag1,
            'tag2': tag2,
        }
        self._local_cache.add('measurement', (benchmark_id, json))

    def run_and_get_output(self, *popenargs, **kwargs):
        '''
        Run command with arguments and return its output.
        '''
        process = subprocess.Popen(*popenargs,
                                   stdout=subprocess.PIPE,
                                   **kwargs)
        try:
            output, unused_err = process.communicate()
        except:
            process.kill()
            process.wait()
            raise
        retcode = process.poll()
        if retcode:
            raise Exception(
                'Process exited with value: %s. Args: %s. Output: %s' %
                (retcode, process.args, output))
        return output

    def git_commit_id_branch_and_date_from_path(self, repo_path):
        '''
        :param repo_path:

        :return tuple(str, str):
            Returns a tuple with the commit_id and the commit_date obtained from the given path
            (the repo_path must the one containing the .git folder or a sub-directory).
        '''
        if not os.path.exists(repo_path):
            raise OSError('The path: %s does not exist.' % (repo_path, ))

        while not os.path.exists(os.path.join(repo_path, '.git')):
            initial = repo_path
            repo_path = os.path.dirname(repo_path)
            if initial == repo_path or not repo_path:
                raise OSError(
                    'The path: %s does not seem to be a git-managed path.' %
                    (repo_path, ))

        commit_id = self.run_and_get_output(
            'git rev-parse HEAD'.split()).strip().decode('utf-8')
        branch = self.run_and_get_output(
            'git rev-parse --abbrev-ref HEAD'.split()).strip().decode('utf-8')
        commit_date = self.run_and_get_output(
            ['git', 'show', '-s', '--format=%ct',
             commit_id]).strip().decode('utf-8')

        commit_date = datetime.datetime.utcfromtimestamp(int(commit_date))
        return commit_id, branch, commit_date