def test_sextractor_not_installed(self, which_mock): which_mock.return_value = [] with self.assertRaises(astromatic.SExtractorNotInstalled): astromatic.sextractor_version() which_mock.assert_has_calls( [mock.call('sextractor'), mock.call('sex')])
def test_sextractor_version(self): # We have no other way of knowing the SExtractor version that is # installed on the system than running it ourselves with the --version # option and examine its output: if sextractor_version() returns the # tuple (2, 8, 6), for example, that means that the SExtractor version # number must contain the string '2.8.6'. What we are doing, basically, # is to test the functionality the other way around: we take the tuple # that sextractror_version() outputs, transform it back to a string and # verify that it corresponds to what the --version option prints. try: executable = methods.which(*astromatic.SEXTRACTOR_COMMANDS)[0] except IndexError: msg = "SExtractor not found in the current environment" raise astromatic.SExtractorNotInstalled(msg) with tempfile.TemporaryFile() as fd: args = [executable, '--version'] subprocess.check_call(args, stdout=fd) fd.seek(0) # For example: "SExtractor version 2.5.0 (2006-07-14)" stdout = '\n'.join(fd.readlines()) version = astromatic.sextractor_version() # From, for example, (2, 5, 0) to '2.5.0' version_str = '.'.join(str(x) for x in version) self.assertTrue(version_str in stdout) # SExtractorNotInstalled must be raised if no SExtractor executable is # detected. In order to simulate this, mock os.environ and clear the # 'PATH' environment variable. In this manner, as the list of paths to # directories where executables may be found is empty, SExtractor (and # any other command) will appear as not installed on the system. environment_copy = copy.deepcopy(os.environ) with mock.patch.object(os, 'environ', environment_copy) as mocked: mocked['PATH'] = '' with self.assertRaises(astromatic.SExtractorNotInstalled): astromatic.sextractor_version()
def test_sextractor(self): # Note that, being a third-party program, we cannot write a unit test # to check that SExtractor works as it should (i.e., that it correctly # detects astronomical sources on FITS images): that is something that # Emmanuel Bertin, its developer, certainly takes care of. What we must # test for is whether astromatic.sextractor(), our Python wrapper to # call SExtractor, works as we expect and raises the appropriate errors # when something goes wrong. Nothing less, nothing more. # First, the most probable scenario: our wrapper should run SExtractor # on any FITS image that it receives (here we use some downloaded from # the STScI Digitized Sky Survey) and return the path to the output # catalog, which astromatic.Catalog should be always able to parse. kwargs = dict(stdout=open(os.devnull), stderr=open(os.devnull)) for img_path in dss_images.TEST_IMAGES: catalog_path = astromatic.sextractor(img_path, **kwargs) try: Catalog(catalog_path) finally: os.unlink(catalog_path) # SExtractorNotInstalled must be raised if no SExtractor executable is # detected. In order to simulate this, mock os.environ and clear the # 'PATH' environment variable. In this manner, as the list of paths to # directories where executables may be found is empty, SExtractor (and # any other command) will appear as not installed on the system. environment_copy = copy.deepcopy(os.environ) with mock.patch.object(os, 'environ', environment_copy) as mocked: mocked['PATH'] = '' with self.assertRaises(astromatic.SExtractorNotInstalled): astromatic.sextractor(img_path) # IOError is raised if any of the four SExtractor configuration files # is not readable or does no exist. To test that, mock the module-level # variables so that they first refer to an unreadable file and later to # a nonexistent one. for variable in self.SEXTRACTOR_MODULE_VARS: path = eval('astromatic.%s' % variable) ext = os.path.splitext(path)[1] copy_path = get_nonexistent_path(ext=ext) shutil.copy2(path, copy_path) with mock.patch.object(astromatic, variable, copy_path): # chmod u-r mode = stat.S_IMODE(os.stat(copy_path)[stat.ST_MODE]) mode ^= stat.S_IRUSR os.chmod(copy_path, mode) args = IOError, astromatic.sextractor, img_path self.assertFalse(os.access(copy_path, os.R_OK)) self.assertRaises(*args) os.unlink(copy_path) self.assertFalse(os.path.exists(copy_path)) self.assertRaises(*args) # SExtractorUpgradeRequired must be raised if the version of SExtractor # that is installed on the system is older than that defined in the # SEXTRACTOR_REQUIRED_VERSION module-level variable. We cannot (easily, # at least) change the version that is installed, but we can test for # this by changing the value of the required version, setting it to a # tuple that is greater than the installed version of SExtractor. # From, for example, (2, 8, 6) to (2, 8, 7) version = list(astromatic.sextractor_version()) version[-1] += 1 version = tuple(version) with mock.patch.object(astromatic, 'SEXTRACTOR_REQUIRED_VERSION', version): with self.assertRaises(astromatic.SExtractorUpgradeRequired): astromatic.sextractor(img_path) # The SExtractorError exception is raised if anything goes wrong during # the execution of SExtractor (in more technical terms, if its return # code is other than zero). For example, the FITS image may not exist, # or a non-numerical value may be assigned to a parameter that expects # one, or we could try to run SExtractor on a FITS extension that does # not exist. These three are just examples: SExtractor may fail for # many different reasons that we cannot even foresee. # (1) Try to run SExtractor on a non-existent image kwargs = dict(stdout=open(os.devnull), stderr=open(os.devnull)) nonexistent_path = get_nonexistent_path(ext='.fits') with self.assertRaises(astromatic.SExtractorError): astromatic.sextractor(nonexistent_path, **kwargs) # (2) The DETECT_MINAREA parameter (minimum number of pixels above the # threshold needed to trigger detection) expects an integer. Assigning # to it a string will cause SExtractor to complain ("keyword out of # range") and abort its execution. kwargs['options'] = dict(DETECT_MINAREA='Y') with self.assertRaises(astromatic.SExtractorError): astromatic.sextractor(img_path, **kwargs) del kwargs['options'] # (3) Try to run SExtractor on a nonexistent FITS extension. hdulist = pyfits.open(img_path, mode='readonly') nextensions = len(hdulist) hdulist.close() kwargs['ext'] = nextensions + 1 with self.assertRaises(astromatic.SExtractorError): astromatic.sextractor(img_path, **kwargs) # TypeError raised if 'options' is not a dictionary kwargs = dict(options=['DETECT_MINAREA', '5']) with self.assertRaises(TypeError): astromatic.sextractor(img_path, **kwargs) # ... or if any of its elements is not a string. kwargs['options'] = {'DETECT_MINAREA': 125} with self.assertRaises(TypeError): astromatic.sextractor(img_path, **kwargs) # TypeError also raised if 'ext' is not an integer... kwargs = dict(ext=0.56) with self.assertRaises(TypeError): astromatic.sextractor(img_path, **kwargs) # ... even if it is a float but has nothing after the decimal point # (for which the built-in is_integer() function would return True). kwargs = dict(ext=0.0) with self.assertRaises(TypeError): astromatic.sextractor(img_path, **kwargs)
def test_sextractor_version(self, check_output_mock): check_output_mock.return_value = "SExtractor version 2.19.5 (2014-03-19)\n" self.assertEqual((2, 19, 5), astromatic.sextractor_version()) check_output_mock.assert_called_once()
match = re.match(regexp, version) if match is None: msg = "cannot extract version from '%s' (%s)" % (version, module) raise Exception(msg) else: version = match.group(0) return str_to_version(version) # For each module whose minimum version has been defined in requirements.txt, # create an import hook and add it to sys.meta_path, which is searched before # any implicit default finders or sys.path. for module, version in [ ("numpy", (1, 7, 1)), ("aplpy", (0, 9, 9)), ("scipy", (0, 12, 0)), ("matplotlib", (1, 2, 1)), ("mock", (1, 0, 1)), ("pyfits", (3, 1, 2)), ("pyraf", (2, 1, 1)), ("uncertainties", (2, 4, 1)), ]: hook = RequireModuleVersionHook(module, version, get__version__) sys.meta_path.append(hook) # If the minimum version is not met, raise SExtractorUpgradeRequired as # soon as possible, instead of waiting until we attempt to run SExtractor. if astromatic.sextractor_version() < astromatic.SEXTRACTOR_REQUIRED_VERSION: raise astromatic.SExtractorUpgradeRequired()
regexp = "\d+(\.\d+)+" match = re.match(regexp, version) if match is None: msg = "cannot extract version from '%s' (%s)" % (version, module) raise Exception(msg) else: version = match.group(0) return str_to_version(version) # For each module whose minimum version has been defined in requirements.txt, # create an import hook and add it to sys.meta_path, which is searched before # any implicit default finders or sys.path. for module, version in [('numpy', (1, 7, 1)), ('aplpy', (0, 9, 9)), ('scipy', (0, 12, 0)), ('matplotlib', (1, 2, 1)), ('mock', (1, 0, 1)), ('pyfits', (3, 1, 2)), ('pyraf', (2, 1, 1)), ('uncertainties', (2, 4, 1))]: hook = RequireModuleVersionHook(module, version, get__version__) sys.meta_path.append(hook) # If the minimum version is not met, raise SExtractorUpgradeRequired as # soon as possible, instead of waiting until we attempt to run SExtractor. sextractor_version = astromatic.sextractor_version() sextractor_minimum = astromatic.SEXTRACTOR_REQUIRED_VERSION if not sextractor_version >= sextractor_minimum: msg = "SExtractor >= %s is required, found %s" args = (version_to_str(sextractor_minimum), version_to_str(sextractor_version)) raise astromatic.SExtractorUpgradeRequired(msg % args)