Coverage for tests / test_packages.py: 12%

103 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-26 08:43 +0000

1# This file is part of utils. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22import os 

23import unittest 

24from collections.abc import Mapping 

25 

26import lsst.utils.packages 

27import lsst.utils.tests 

28 

29TESTDIR = os.path.abspath(os.path.dirname(__file__)) 

30 

31 

32class PackagesTestCase(unittest.TestCase): 

33 """Tests for package version collection. 

34 

35 Unfortunately, we're somewhat limited in what we can test because 

36 we only get the versions of things being used at runtime, and this 

37 package sits rather low in the dependency chain so there's not 

38 usually a lot of other packages available when this test gets run. 

39 Therefore some of the tests are only checking that things don't 

40 explode, since they are incapable of testing much more than that. 

41 """ 

42 

43 def testPython(self): 

44 """Test that we get the right version for this python package.""" 

45 versions = lsst.utils.packages.getPythonPackages() 

46 expected = lsst.utils.version.__version__ 

47 self.assertEqual(versions["utils"], expected) 

48 

49 # Check that standard python library packages are not included. 

50 self.assertNotIn("os", versions) 

51 

52 # Should always include the python version information. 

53 self.assertIn("python", versions) 

54 

55 # Also for all installed distributions. 

56 versions2 = lsst.utils.packages.getAllPythonDistributions() 

57 self.assertEqual(versions2["utils"], expected) 

58 self.assertNotIn("os", versions2) 

59 self.assertIn("python", versions2) 

60 

61 # Packages import yaml and so it should have a version. 

62 # yaml is a package but PyYAML is the distribution. 

63 self.assertEqual(versions["yaml"], versions2["PyYAML"]) 

64 

65 def testEnvironment(self): 

66 """Test getting versions from the environment. 

67 

68 Unfortunately, none of the products that need their versions divined 

69 from the environment are dependencies of this package, and so all we 

70 can do is test that this doesn't fall over. 

71 """ 

72 lsst.utils.packages.getEnvironmentPackages() 

73 lsst.utils.packages.getEnvironmentPackages(include_all=True) 

74 

75 def testConda(self): 

76 """Test getting versions from conda environment. 

77 

78 We do not rely on being run in a conda environment so all we can do is 

79 test that this doesn't fall over. 

80 """ 

81 lsst.utils.packages.getCondaPackages() 

82 

83 def _writeTempFile(self, packages, suffix): 

84 """Write packages to a temp file using the supplied suffix and read 

85 back. 

86 """ 

87 with lsst.utils.tests.getTempFilePath(suffix) as tempName: 

88 packages.write(tempName) 

89 new = lsst.utils.packages.Packages.read(tempName) 

90 return new 

91 

92 def testPackages(self): 

93 """Test the Packages class.""" 

94 packages = lsst.utils.packages.Packages.fromSystem() 

95 self.assertIsInstance(packages, Mapping) 

96 

97 # Check that stringification is not crashing. 

98 self.assertTrue(str(packages).startswith("Packages({")) 

99 self.assertTrue(repr(packages).startswith("Packages({")) 

100 

101 # Test pickling and YAML 

102 new = self._writeTempFile(packages, ".pickle") 

103 new_pkl = self._writeTempFile(packages, ".pkl") 

104 new_yaml = self._writeTempFile(packages, ".yaml") 

105 new_json = self._writeTempFile(packages, ".json") 

106 

107 self.assertIsInstance(new, lsst.utils.packages.Packages, f"Checking type ({type(new)}) from pickle") 

108 self.assertIsInstance( 

109 new_yaml, lsst.utils.packages.Packages, f"Checking type ({type(new_yaml)}) from YAML" 

110 ) 

111 self.assertEqual(new, packages) 

112 self.assertEqual(new_pkl, new) 

113 self.assertEqual(new, new_yaml) 

114 self.assertEqual(new, new_json) 

115 

116 # Dict compatibility. 

117 for k, v in new.items(): 

118 self.assertEqual(new[k], v) 

119 

120 with self.assertRaises(ValueError): 

121 self._writeTempFile(packages, ".txt") 

122 

123 with self.assertRaises(ValueError): 

124 # .txt extension is not recognized 

125 lsst.utils.packages.Packages.read("something.txt") 

126 

127 # 'packages' and 'new' should have identical content 

128 self.assertDictEqual(packages.difference(new), {}) 

129 self.assertDictEqual(packages.missing(new), {}) 

130 self.assertDictEqual(packages.extra(new), {}) 

131 self.assertEqual(len(packages), len(new)) 

132 

133 # Check inverted comparisons 

134 self.assertDictEqual(new.difference(packages), {}) 

135 self.assertDictEqual(new.missing(packages), {}) 

136 self.assertDictEqual(new.extra(packages), {}) 

137 

138 # Check comparison functionality. Can not import a package that we 

139 # do not know is present (and standard library packages are not 

140 # reported) so instead pretend. 

141 new_package = "pretend_package" 

142 self.assertNotIn(new_package, packages) 

143 

144 new = lsst.utils.packages.Packages(packages.copy()) 

145 new[new_package] = new["python"] 

146 self.assertDictEqual(packages.difference(new), {}) # No inconsistencies 

147 self.assertDictEqual(packages.extra(new), {}) # Nothing in 'packages' that's not in 'new' 

148 missing = packages.missing(new) 

149 self.assertGreater(len(missing), 0) # 'packages' should be missing some stuff in 'new' 

150 self.assertIn(new_package, missing) 

151 

152 # Inverted comparisons 

153 self.assertDictEqual(new.difference(packages), {}) 

154 self.assertDictEqual(new.missing(packages), {}) # Nothing in 'new' that's not in 'packages' 

155 extra = new.extra(packages) 

156 self.assertGreater(len(extra), 0) # 'new' has extra stuff compared to 'packages' 

157 self.assertIn(new_package, extra) 

158 self.assertIn(new_package, new) 

159 

160 # Run with both a Packages and a dict 

161 for new_pkg in (new, dict(new)): 

162 packages.update(new_pkg) # Should now be identical 

163 self.assertDictEqual(packages.difference(new_pkg), {}) 

164 self.assertDictEqual(packages.missing(new_pkg), {}) 

165 self.assertDictEqual(packages.extra(new_pkg), {}) 

166 self.assertEqual(len(packages), len(new_pkg)) 

167 

168 # Loop over keys to check iterator. 

169 keys = set(new) 

170 self.assertEqual(keys, set(dict(new).keys())) 

171 

172 # Loop over values to check that we do get them all. 

173 values = set(new.values()) 

174 self.assertEqual(values, set(dict(new).values())) 

175 

176 # Serialize via bytes 

177 for format in ("pickle", "yaml", "json"): 

178 asbytes = new.toBytes(format) 

179 from_bytes = lsst.utils.packages.Packages.fromBytes(asbytes, format) 

180 self.assertEqual(from_bytes, new) 

181 

182 with self.assertRaises(ValueError): 

183 new.toBytes("unknown_format") 

184 

185 with self.assertRaises(ValueError): 

186 lsst.utils.packages.Packages.fromBytes(from_bytes, "unknown_format") 

187 

188 with self.assertRaises(TypeError): 

189 some_yaml = b"list: [1, 2]" 

190 lsst.utils.packages.Packages.fromBytes(some_yaml, "yaml") 

191 

192 # Check that "all" packages runs and does not include stdlib. 

193 all = lsst.utils.packages.Packages.fromSystem(include_all=True) 

194 self.assertNotIn("os", all) 

195 

196 def testBackwardsCompatibility(self): 

197 """Test if we can read old data files.""" 

198 # Pickle contents changed when moving to dict base class. 

199 packages_p = lsst.utils.packages.Packages.read(os.path.join(TESTDIR, "data", "v1.pickle")) 

200 self.assertIsInstance(packages_p, lsst.utils.packages.Packages) 

201 

202 # YAML format is unchanged when moving from special class to dict 

203 # but test anyway. 

204 packages_y = lsst.utils.packages.Packages.read(os.path.join(TESTDIR, "data", "v1.yaml")) 

205 

206 self.assertEqual(packages_p, packages_y) 

207 

208 # Now check that we can read the version 2 files that were 

209 # written with Packages inheriting from dict but still in `base`. 

210 packages_p2 = lsst.utils.packages.Packages.read(os.path.join(TESTDIR, "data", "v2.pickle")) 

211 packages_y2 = lsst.utils.packages.Packages.read(os.path.join(TESTDIR, "data", "v2.yaml")) 

212 self.assertEqual(packages_p2, packages_y2) 

213 self.assertIsInstance(packages_p2, lsst.utils.packages.Packages) 

214 

215 

216if __name__ == "__main__": 

217 unittest.main()