Example #1
0
    def __init__(self):
        Tk.__init__(self)
        self.overrideredirect(1)  # eliminate title bar

        ws = self.winfo_screenwidth()  # width of the screen
        hs = self.winfo_screenheight()  # height of the screen

        welcome_screen = ["Logo1_800.gif", "Logo2_800.gif"]
        path_welcome_screen = SrcPath.abs("gui", welcome_screen[randint(0, 1)])
        background_image = PhotoImage(file=path_welcome_screen)
        win_width = 13.9 / 14.0 * background_image.width(
        )  # eliminates root bg
        win_height = 13.9 / 14.0 * background_image.height()

        # calculate x and y coordinates for the Tk root window
        x_position = (ws * 1.0 / 2.0) - (win_width * 1.0 / 2.0)
        y_position = (hs * 1.0 / 2.0) - (win_height * 1.0 / 2.0)

        background_label = Label(self, image=background_image)
        background_label.image = background_image  # don't remove this line!
        # eliminates root bezels
        background_label.place(relx=0.5, rely=0.5, anchor=CENTER)

        self.geometry('%dx%d+%d+%d' %
                      (win_width, win_height, x_position, y_position))
        self.wait_visibility()
        self.lift()  # place above all window
        self.after(2000, self.destroy)
Example #2
0
 def __init__(self, master):
     Frame.__init__(self, master)
     path_gears = SrcPath.abs("gui", "gears.gif")
     gears_image = PhotoImage(file=path_gears)
     gears_label = Label(self, image=gears_image)
     gears_label.image = gears_image  # don't remove this line!
     gears_label.place(relx=0.5, rely=0.5, anchor=CENTER)
    def test_cache_invalidation(self):
        # typical initialization
        config = Helper.read_conf(SrcPath.abs("config.yml"))
        db_name = os.path.join(self.data_path, "requests.db")
        db = UpdateRequestDB(db_name,
                             config['gh_api_token'],
                             self.DB_REPO_NAME)
        db.fetch_all(force_full_sync=True)
        self.assertGreater(len(db.requests), 7)

        # initialize with different repository
        with shelve.open(db_name) as cache:
            cache['repo'] = 'SOMETHING ELSE'
        db = UpdateRequestDB(db_name,
                             config['gh_api_token'],
                             self.DB_REPO_NAME)
        self.assertLess(len(db.requests), 7)

        db.fetch_all(force_full_sync=True)
        self.assertGreater(len(db.requests), 7)

        # initialize with corrupted data
        with shelve.open(db_name) as cache:
            cache['1'] = None
            cache['2'] = 'a string'
            cache['3'] = 123
            cache['4'] = ['42', None]
            cache['5'] = 3.14
            cache['6'] = {1, 2, 34}
        db = UpdateRequestDB(db_name,
                             config['gh_api_token'],
                             self.DB_REPO_NAME)
        self.assertLess(len(db.requests), 7)
    def test_repo_manager(self):
        config_file = SrcPath.abs('config.yml')
        config = Helper.read_conf(config_file)
        url = ('https://%s:%[email protected]/teammask/hello-world.git' %
               (config['gh_username'], config['gh_api_token']))
        repo_path = os.path.join(self.SHARED_PATH, "test_repo")
        repo = RepoManager(repo_path, url)
        self.assertIsNotNone(repo)
        self.assertTrue(repo.root == repo_path)
        self.assertTrue(path.isdir(repo.root))
        self.assertTrue(repo.oec.description == RepoManager.REPO_BANNER)

        # Test commit: - Modify the readme file on master
        with open(os.path.join(repo_path, "README.md"), 'w') as readme_file:
            readme_file.write("Modified at: " + str(datetime.utcnow()))
        commit_hash = repo.commit("Test commit at: " + str(datetime.utcnow()))
        self.assertTrue(commit_hash != "")
        # forget last commit
        repo.oec.git.reset('--hard', 'HEAD~1')

        # Test branch and del_branch:-
        branch_result = repo.branch("ABCDE")
        self.assertTrue(branch_result == "ABCDE")
        self.assertTrue(repo.oec.active_branch.name == "ABCDE")
        self.assertTrue(repo.delete_branch("ABCDE", False))
        for branch in repo.oec.branches:
            self.assertTrue(branch.name != "ABCDE")
        branch_result = repo.branch("ABCDEG")

        # Test push
        self.assertTrue(repo.push(branch_result))
        found = False
        for branch in repo.oec.remote().refs:
            if branch.name == "origin/ABCDEG":
                found = True
                break
        self.assertTrue(found)
        # technically no branch was pushed if the commit reset was okay but
        # this line is here for good.
        repo.delete_branch("ABCDEG", True)

        # Test Pull. Any further testing is testing gitPython which is not
        # relevant
        self.assertTrue(repo.pull())

        # Test Destroy
        repo.destroy()
        self.assertIsNone(repo.oec)
        self.assertTrue(repo.root == "")
        self.assertFalse(path.isdir(repo_path))
    def test_get_similar(self):
        config = Helper.read_conf(SrcPath.abs("config.yml"))
        db_name = os.path.join(self.SHARED_PATH, "dbtest_requests.db")

        # initialize the db and fetch the latest requests
        db = UpdateRequestDB(db_name,
                             config['gh_api_token'],
                             self.DB_REPO_NAME)
        db.fetch_all(force_full_sync=False)

        # create a request that's the same as PR 1
        duplicate_request = UpdateRequest(
            PlanetarySysUpdate(
                    "HAT-P-55",
                    planets=[
                        PlanetUpdate("HAT-P-55 b",
                                     fields={
                                         "eccentricity": Quantity("0.139000"),
                                         "impactparameter": Quantity(
                                             "0.3920",
                                             error=("0.0860", "0.0730")),
                                         "period": Quantity(
                                             "3.58524670",
                                             unit='days',
                                             error=("0.00000640", "0.00000640")
                                         )
                                     })
                    ]),
            title="Test request",
            message="message",
            reference="UpdateRequestDBTest"
        )

        similar = db.get_similar(duplicate_request)
        self.assertIsNotNone(similar)
        self.assertEqual(1, similar.request.pullreq_num)

        # try submitting the request,
        # although it will always fail because we haven't pushed the branch
        # the db should still figure out it's a duplicate
        with self.assertRaises(DuplicateError):
            db.submit(duplicate_request)

        duplicate_request.updates.name = "HAT-P-42"
        similar = db.get_similar(duplicate_request)
        self.assertIsNone(similar)
    def test_fetch(self):
        def verify_db(
                db_requests: Dict[str, Union[CachedRequest, IgnoredRequest]]):
            """Verifies the request db has the correct data."""
            # 1 is valid and open
            request_1 = db_requests['1'].request
            self.assertEqual("Update HAT-P-55", request_1.title)
            self.assertEqual("HAT-P-55", request_1.updates.name)
            self.assertEqual(1, len(request_1.updates.planets))
            self.assertEqual(3, len(request_1.updates.planets[0].fields))
            self.assertEqual(1, request_1.pullreq_num)
            self.assertFalse(request_1.rejected)

            # 8 is invalid
            self.assertEqual(IgnoredRequest.invalid, db_requests['8'])

            # 4 has been merged
            self.assertEqual(IgnoredRequest.merged, db_requests['4'])

            # 7 is valid and closed
            request_7 = db_requests['7'].request
            self.assertEqual("HD 66428", request_7.updates.name)
            self.assertTrue(request_7.rejected)

        config = Helper.read_conf(SrcPath.abs("config.yml"))
        db_name = os.path.join(self.data_path, "requests.db")

        # initialize the db and fetch the latest requests
        db = UpdateRequestDB(db_name,
                             config['gh_api_token'],
                             self.DB_REPO_NAME)
        db.fetch_all(force_full_sync=True)

        request_ids = set(db.requests.keys())
        self.assertGreater(len(request_ids), 1)
        verify_db(db.requests)

        # re-initialize the db without fetching
        db = UpdateRequestDB(db_name,
                             config['gh_api_token'],
                             self.DB_REPO_NAME)
        verify_db(db.requests)
Example #7
0
    def _reload_cat_config(self) -> None:
        """
        Reload catalogue config files.
        """
        del self.cats[:]

        # TODO: they will be fetched from a git repo in the future
        pattern = SrcPath.abs(self.config['cat_config_path'], '*.yml')
        for f in glob.glob(pattern):
            try:
                with open(f, 'r') as fin:
                    cat_config = CatalogueConfig(fin)
                    cat = MonitoredCatalogue(cat_config)
                    self.cats.append(cat)
            except Exception as e:
                logging.error('Failed loading config "%s": %s' % (f, e))

        if len(self.cats) == 0:
            logging.warning('No catalogue configurations found in "%s".' %
                            pattern)
Example #8
0
    def fetch(self) -> None:
        """
        Fetch the latest csv from monitored catalogue
        """
        reader = None

        # debug feature - load file locally
        debug_file = self.config.raw.get('debug_file')
        if debug_file:
            debug_file = SrcPath.abs(debug_file)
            with open(debug_file, 'r') as f:
                # read the entire file so we can close it right away
                reader = csv.DictReader(StringIO(f.read()))
                logging.debug("Loaded catalogue from local debug file")

        if not reader:
            f = requests.get(self.config.url)
            result = f.text
            reader = csv.DictReader(StringIO(result))

        # search the index of required field in raw field list
        # self._search_index(raw_col)
        # loop through each row in the csv

        self.systems = dict()

        def try_get_field(row: Dict[str, str], fielddesc: Dict[str, str],
                          oec_field_name: str):
            cat_field_name = fielddesc.get(oec_field_name)
            if cat_field_name:
                row_value = row.get(cat_field_name)
                return row_value

        for next_planet in reader:
            system_name = next_planet[self.config.system_name]

            planet_name = None
            if self.config.planet_name:
                planet_name = next_planet[self.config.planet_name]
            else:
                planet_name = system_name + ' ' +\
                              next_planet[self.config.planet_letter]

            pl = Planet(planet_name, system_name)

            for oec_field, cat_fieldmeta in self.config.field_map.items():
                value = next_planet.get(cat_fieldmeta['name'])
                if value:
                    errorminus = try_get_field(next_planet, cat_fieldmeta,
                                               'errorminus')
                    errorplus = try_get_field(next_planet, cat_fieldmeta,
                                              'errorplus')
                    lowerlimit = try_get_field(next_planet, cat_fieldmeta,
                                               'lowerlimit')
                    upperlimit = try_get_field(next_planet, cat_fieldmeta,
                                               'upperlimit')
                    limitflag = try_get_field(next_planet, cat_fieldmeta,
                                              'limit_flag')

                    explicit_limit = lowerlimit or upperlimit

                    errors = (lowerlimit, upperlimit)\
                        if explicit_limit else (errorminus, errorplus)

                    q = Quantity(
                        value, cat_fieldmeta.get('unit'), errors,
                        bool(explicit_limit
                             or (limitflag is None and bool(limitflag))))

                    # convert the quantity if it's using different unit
                    oec_fieldmeta = oec.PLANET_FIELDS[oec_field]
                    if oec_fieldmeta.unit != cat_fieldmeta.get('unit'):
                        q = q.to(oec_fieldmeta.unit)
                    pl.prop[oec_field] = q

            sys = self.systems.get(system_name)
            if sys is None:
                self.systems[system_name] = sys = System(system_name)
            sys.planets.append(pl)
class BaseTestCase(unittest.TestCase):
    # root folder containing tests
    TESTS_ROOT = SrcPath.abs('tests')

    # this folder will be ignored by git
    TEST_DATA = os.path.join(TESTS_ROOT, '.cache')

    # this folder contains private folders for each test run
    INDIVIDUAL_PATH = os.path.join(TEST_DATA, 'individual')

    # this folder contains everything that's shared among tests
    SHARED_PATH = os.path.join(TEST_DATA, 'shared')

    # private folders created for the last test run
    data_paths = []

    # outcome of the last test run
    result = None

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.data_path = None

    @classmethod
    def setUpClass(cls):
        """
        Injects our `setUp` or `tearDown` method to the inherited test case.
        """
        if cls is not BaseTestCase:
            if cls.setUp is not BaseTestCase.setUp:
                orig_setup = cls.setUp

                def setup_override(self):
                    """Overridden setup method"""
                    BaseTestCase.setUp(self)
                    return orig_setup(self)

                cls.setUp = setup_override
                # if cls.tearDown is not BaseTestCase.tearDown:
                #     orig_teardown = cls.tearDown
                #
                #     def teardown_override(self):
                #         """Overridden teardown method"""
                #         BaseTestCase.tearDown(self)
                #         return orig_teardown(self)
                #     cls.tearDown = teardown_override

    @classmethod
    def tearDownClass(cls):
        # clean up testing data if all tests passed
        if BaseTestCase.result and BaseTestCase.result.wasSuccessful():
            for path in BaseTestCase.data_paths:
                try:
                    shutil.rmtree(path, onerror=cls.__del_helper)
                except FileNotFoundError as e:
                    pass
                except Exception as e:
                    logging.exception(e)

    def run(self, result=None):
        # keep track of the latest test result
        if not result:
            result = self.defaultTestResult()
        BaseTestCase.result = result
        return unittest.TestCase.run(self, result)

    def setUp(self):
        # create shared folder if it doesn't exist
        os.makedirs(self.SHARED_PATH, exist_ok=True)

        # setup folder for the current test
        test_run = datetime.now().strftime("%Y%m%d_%H%M%S") + '_' + self.id()
        test_folder = os.path.join(BaseTestCase.INDIVIDUAL_PATH, test_run)
        os.makedirs(test_folder, exist_ok=True)
        self.data_path = test_folder
        if test_folder not in self.data_paths:
            self.data_paths.append(test_folder)

    @classmethod
    def __del_helper(cls, func, dir_path, exc):
        os.chmod(dir_path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)  # 0777
        func(dir_path)

    def clear_data_path(self):
        """
        Clear application data path for running the current test case
        """
        for file in os.listdir(self.data_path):
            fname = os.path.join(self.data_path, file)
            try:
                if os.path.isfile(fname):
                    os.unlink(fname)
                elif os.path.isdir(fname):
                    shutil.rmtree(fname, onerror=self.__del_helper)
            except Exception as e:
                print(e)

    ###########################################################################
    # Assertions
    ###########################################################################
    def assertPathEqual(self, path1, path2, msg=None):
        self.assertEqual(os.path.normpath(path1), os.path.normpath(path2), msg)
Example #10
0
class Quantity:
    """
    Representation of a quantity and associated error in a specific
    unit system.
    """
    # Load unit registry from module resources
    _UR = UnitRegistry()
    _UR.load_definitions(SrcPath.abs('sync/resources/units.txt'))
    NUM_REGEX = re.compile(R'[-+]?\d*\.?\d+([eE][-+]?\d+)?')

    def __init__(self,
                 value: str,
                 unit: str = None,
                 error: Tuple[str, str] = None,
                 is_limit: bool = False):
        """
        Construct a Quantity object.
        :param value: The quantity value.
        :param unit: The unit system.
        :param error: The lower bound and upper bound of error.
        :param is_limit: If set to True, the error tuple represents lower limit
                         and upper limit.
        """
        if not value:
            raise ValueError("Value cannot be empty: " + repr(value))
        value = value.strip()
        if not self._is_num(value):
            raise ValueError("Invalid number: " + repr(value))
        if error and not (self._is_num(error[0]) and self._is_num(error[1])):
            error = None  # errors must be defined in a pair

        if error and not is_limit:
            # error-minus and error-plus pair should be non-negative
            error = (str(abs(Decimal(error[0]))), str(abs(Decimal(error[1]))))

        self.value = value
        self.unit = unit
        self.error = error
        self.is_limit = is_limit

    def __eq__(self, other: 'Quantity'):
        # keep the type quoted ^, class is incomplete at this point

        # value
        if Decimal(self.value) != Decimal(other.value):
            return False
        # unit
        if not self._eq_unit(self.unit, other.unit):
            return False
        return True

    def __repr__(self):
        return "Quantity(" + repr(self.value) + "," + repr(self.unit) + "," + \
               repr(self.error) + "," + repr(self.is_limit) + ")"

    @classmethod
    def _is_num(cls, value: str):
        """
        Returns whether a string value is a valid value
        :param value: a string representing a number
        """
        return value is not None and bool(cls.NUM_REGEX.fullmatch(value))

    @classmethod
    def _eq_unit(cls, a: str, b: str) -> bool:
        if (a is None) != (b is None):
            return False
        if a is not None and cls._UR.get_name(a) != cls._UR.get_name(b):
            return False
        return True

    def to(self, new_unit: str) -> 'Quantity':
        """
        Convert to another unit system.
        :param new_unit: The target unit system.
        :return: A new quantity value using the target unit system
        """
        def conv(original: str) -> str:
            """
            Converts a value from current unit to the new_unit
            :param original: string representing a number
            :return: string representing converted number
            """
            # Decimal type is not so well supported in pint package
            # but it preserves precisions and doesn't have numeric errors
            #
            # It fails if the conversion between two units involves an offset
            # e.g. Kelvin -> Celsius
            old = self._UR.Quantity(Decimal(original), self.unit)
            new = old.to(new_unit)
            return str(new.magnitude)

        return Quantity(
            conv(self.value), new_unit, None if not self.error else (
                conv(self.error[0]),
                conv(self.error[1]),
            ), self.is_limit)

    def get_error_or_limit(self, is_limit: bool) -> Tuple[str, str]:
        """
        Get the error/limit tuple:
            (errorminus, errorplus), or
            (lowerlimit, upperlimit) from this quantity.
        :param is_limit: return limit tuple? otherwise error tuple
        """
        # conversion not need
        if not (self.error and is_limit != self.is_limit):
            return self.error
        if is_limit:
            # convert to limit/bound tuple
            return (str(Decimal(self.value) - Decimal(self.error[0])),
                    str(Decimal(self.value) + Decimal(self.error[1])))
        else:
            # convert to error tuple
            return (str(Decimal(self.value) - Decimal(self.error[0])),
                    str(Decimal(self.error[1]) - Decimal(self.value)))

    def can_update_error(self, other: 'Quantity') -> bool:
        """
        Returns whether the error term of this quantity can be updated from
        the other quantity.

        Assumes the two quantities already have the same value and unit,
        otherwise the return value makes no sense.
        :param other: the other quantity
        """
        assert self._eq_unit(self.unit, other.unit)

        # the new quantity has no error term
        if not other.error:
            return False

        # this quantity has no error term, can always update
        if not self.error:
            return True

        # they have the same error term
        if self.error == other.error and self.is_limit == other.is_limit:
            return False

        # Cannot conclude yet, because
        # 1. ('0.0', '0.0') equals to ('0', '0')
        # 2. need conversion between errors and limits/bounds
        othererror = other.error

        if self.is_limit != other.is_limit:
            # conversion between errors and limits
            othererror = other.get_error_or_limit(self.is_limit)

        # compare the errors
        for i in range(2):
            if Decimal(self.error[i]) != Decimal(othererror[i]):
                return True

        return False