1 #include "item_updater.hpp"
2 #include "mocked_utils.hpp"
3 
4 #include <sdbusplus/test/sdbus_mock.hpp>
5 
6 #include <gmock/gmock.h>
7 #include <gtest/gtest.h>
8 
9 using namespace phosphor::software::updater;
10 using ::testing::_;
11 using ::testing::Pointee;
12 using ::testing::Return;
13 using ::testing::ReturnArg;
14 using ::testing::StrEq;
15 
16 using std::experimental::any;
17 
18 class TestItemUpdater : public ::testing::Test
19 {
20   public:
21     using Properties = ItemUpdater::Properties;
22     using PropertyType = utils::UtilsInterface::PropertyType;
23 
24     TestItemUpdater() :
25         mockedUtils(
26             reinterpret_cast<const utils::MockedUtils&>(utils::getUtils()))
27     {
28         ON_CALL(mockedUtils, getVersionId(_)).WillByDefault(ReturnArg<0>());
29         ON_CALL(mockedUtils, getPropertyImpl(_, _, _, _, StrEq(PRESENT)))
30             .WillByDefault(Return(any(PropertyType(true))));
31     }
32 
33     ~TestItemUpdater()
34     {
35         utils::freeUtils();
36     }
37 
38     const auto& GetActivations()
39     {
40         return itemUpdater->activations;
41     }
42 
43     std::string getObjPath(const std::string& versionId)
44     {
45         return std::string(dBusPath) + "/" + versionId;
46     }
47 
48     void onPsuInventoryChanged(const std::string& psuPath,
49                                const Properties& properties)
50     {
51         itemUpdater->onPsuInventoryChanged(psuPath, properties);
52     }
53 
54     void scanDirectory(const fs::path& p)
55     {
56         itemUpdater->scanDirectory(p);
57     }
58 
59     static constexpr auto dBusPath = SOFTWARE_OBJPATH;
60     sdbusplus::SdBusMock sdbusMock;
61     sdbusplus::bus::bus mockedBus = sdbusplus::get_mocked_new(&sdbusMock);
62     const utils::MockedUtils& mockedUtils;
63     std::unique_ptr<ItemUpdater> itemUpdater;
64 };
65 
66 TEST_F(TestItemUpdater, ctordtor)
67 {
68     itemUpdater = std::make_unique<ItemUpdater>(mockedBus, dBusPath);
69 }
70 
71 TEST_F(TestItemUpdater, NotCreateObjectOnNotPresent)
72 {
73     constexpr auto psuPath = "/com/example/inventory/psu0";
74     constexpr auto service = "com.example.Software.Psu";
75     constexpr auto version = "version0";
76     std::string objPath = getObjPath(version);
77     EXPECT_CALL(mockedUtils, getPSUInventoryPath(_))
78         .WillOnce(Return(std::vector<std::string>({psuPath})));
79     EXPECT_CALL(mockedUtils, getService(_, StrEq(psuPath), _))
80         .WillOnce(Return(service));
81     EXPECT_CALL(mockedUtils, getVersion(StrEq(psuPath)))
82         .WillOnce(Return(std::string(version)));
83     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
84                                              _, StrEq(PRESENT)))
85         .WillOnce(Return(any(PropertyType(false)))); // not present
86 
87     // The item updater itself
88     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(dBusPath)))
89         .Times(1);
90 
91     // No activation/version objects are created
92     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath)))
93         .Times(0);
94     itemUpdater = std::make_unique<ItemUpdater>(mockedBus, dBusPath);
95 }
96 
97 TEST_F(TestItemUpdater, CreateOnePSUOnPresent)
98 {
99     constexpr auto psuPath = "/com/example/inventory/psu0";
100     constexpr auto service = "com.example.Software.Psu";
101     constexpr auto version = "version0";
102     std::string objPath = getObjPath(version);
103     EXPECT_CALL(mockedUtils, getPSUInventoryPath(_))
104         .WillOnce(Return(std::vector<std::string>({psuPath})));
105     EXPECT_CALL(mockedUtils, getService(_, StrEq(psuPath), _))
106         .WillOnce(Return(service));
107     EXPECT_CALL(mockedUtils, getVersion(StrEq(psuPath)))
108         .WillOnce(Return(std::string(version)));
109     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
110                                              _, StrEq(PRESENT)))
111         .WillOnce(Return(any(PropertyType(true)))); // present
112 
113     // The item updater itself
114     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(dBusPath)))
115         .Times(1);
116 
117     // activation and version object will be added
118     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath)))
119         .Times(2);
120     itemUpdater = std::make_unique<ItemUpdater>(mockedBus, dBusPath);
121 }
122 
123 TEST_F(TestItemUpdater, CreateTwoPSUsWithSameVersion)
124 {
125     constexpr auto psu0 = "/com/example/inventory/psu0";
126     constexpr auto psu1 = "/com/example/inventory/psu1";
127     constexpr auto service = "com.example.Software.Psu";
128     auto version0 = std::string("version0");
129     auto version1 = std::string("version0");
130     auto objPath0 = getObjPath(version0);
131     auto objPath1 = getObjPath(version1);
132 
133     EXPECT_CALL(mockedUtils, getPSUInventoryPath(_))
134         .WillOnce(Return(std::vector<std::string>({psu0, psu1})));
135     EXPECT_CALL(mockedUtils, getService(_, StrEq(psu0), _))
136         .WillOnce(Return(service));
137     EXPECT_CALL(mockedUtils, getService(_, StrEq(psu1), _))
138         .WillOnce(Return(service));
139     EXPECT_CALL(mockedUtils, getVersion(StrEq(psu0)))
140         .WillOnce(Return(std::string(version0)));
141     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu0), _,
142                                              StrEq(PRESENT)))
143         .WillOnce(Return(any(PropertyType(true)))); // present
144     EXPECT_CALL(mockedUtils, getVersion(StrEq(psu1)))
145         .WillOnce(Return(std::string(version1)));
146     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu1), _,
147                                              StrEq(PRESENT)))
148         .WillOnce(Return(any(PropertyType(true)))); // present
149 
150     // The item updater itself
151     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(dBusPath)))
152         .Times(1);
153 
154     // activation and version object will be added
155     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath0)))
156         .Times(2);
157     itemUpdater = std::make_unique<ItemUpdater>(mockedBus, dBusPath);
158 
159     // Verify there is only one activation and it has two associations
160     const auto& activations = GetActivations();
161     EXPECT_EQ(1u, activations.size());
162     const auto& activation = activations.find(version0)->second;
163     const auto& assocs = activation->associations();
164     EXPECT_EQ(2u, assocs.size());
165     EXPECT_EQ(psu0, std::get<2>(assocs[0]));
166     EXPECT_EQ(psu1, std::get<2>(assocs[1]));
167 }
168 
169 TEST_F(TestItemUpdater, CreateTwoPSUsWithDifferentVersion)
170 {
171     constexpr auto psu0 = "/com/example/inventory/psu0";
172     constexpr auto psu1 = "/com/example/inventory/psu1";
173     constexpr auto service = "com.example.Software.Psu";
174     auto version0 = std::string("version0");
175     auto version1 = std::string("version1");
176     auto objPath0 = getObjPath(version0);
177     auto objPath1 = getObjPath(version1);
178 
179     EXPECT_CALL(mockedUtils, getPSUInventoryPath(_))
180         .WillOnce(Return(std::vector<std::string>({psu0, psu1})));
181     EXPECT_CALL(mockedUtils, getService(_, StrEq(psu0), _))
182         .WillOnce(Return(service));
183     EXPECT_CALL(mockedUtils, getService(_, StrEq(psu1), _))
184         .WillOnce(Return(service));
185     EXPECT_CALL(mockedUtils, getVersion(StrEq(psu0)))
186         .WillOnce(Return(std::string(version0)));
187     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu0), _,
188                                              StrEq(PRESENT)))
189         .WillOnce(Return(any(PropertyType(true)))); // present
190     EXPECT_CALL(mockedUtils, getVersion(StrEq(psu1)))
191         .WillOnce(Return(std::string(version1)));
192     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu1), _,
193                                              StrEq(PRESENT)))
194         .WillOnce(Return(any(PropertyType(true)))); // present
195 
196     // The item updater itself
197     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(dBusPath)))
198         .Times(1);
199 
200     // two new activation and version objects will be added
201     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath0)))
202         .Times(2);
203     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath1)))
204         .Times(2);
205     itemUpdater = std::make_unique<ItemUpdater>(mockedBus, dBusPath);
206 
207     // Verify there are two activations and each with one association
208     const auto& activations = GetActivations();
209     EXPECT_EQ(2u, activations.size());
210     const auto& activation0 = activations.find(version0)->second;
211     const auto& assocs0 = activation0->associations();
212     EXPECT_EQ(1u, assocs0.size());
213     EXPECT_EQ(psu0, std::get<2>(assocs0[0]));
214 
215     const auto& activation1 = activations.find(version1)->second;
216     const auto& assocs1 = activation1->associations();
217     EXPECT_EQ(1u, assocs1.size());
218     EXPECT_EQ(psu1, std::get<2>(assocs1[0]));
219 }
220 
221 TEST_F(TestItemUpdater, OnOnePSURemoved)
222 {
223     constexpr auto psuPath = "/com/example/inventory/psu0";
224     constexpr auto service = "com.example.Software.Psu";
225     constexpr auto version = "version0";
226     std::string objPath = getObjPath(version);
227     EXPECT_CALL(mockedUtils, getPSUInventoryPath(_))
228         .WillOnce(Return(std::vector<std::string>({psuPath})));
229     EXPECT_CALL(mockedUtils, getService(_, StrEq(psuPath), _))
230         .WillOnce(Return(service));
231     EXPECT_CALL(mockedUtils, getVersion(StrEq(psuPath)))
232         .WillOnce(Return(std::string(version)));
233     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
234                                              _, StrEq(PRESENT)))
235         .WillOnce(Return(any(PropertyType(true)))); // present
236 
237     // The item updater itself
238     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(dBusPath)))
239         .Times(1);
240 
241     // activation and version object will be added
242     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath)))
243         .Times(2);
244     itemUpdater = std::make_unique<ItemUpdater>(mockedBus, dBusPath);
245 
246     // the activation and version object will be removed
247     Properties p{{PRESENT, PropertyType(false)}};
248     EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(objPath)))
249         .Times(2);
250     onPsuInventoryChanged(psuPath, p);
251 
252     // on exit, item updater is removed
253     EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(dBusPath)))
254         .Times(1);
255 }
256 
257 TEST_F(TestItemUpdater, OnOnePSUAdded)
258 {
259     constexpr auto psuPath = "/com/example/inventory/psu0";
260     constexpr auto service = "com.example.Software.Psu";
261     constexpr auto version = "version0";
262     std::string objPath = getObjPath(version);
263     EXPECT_CALL(mockedUtils, getPSUInventoryPath(_))
264         .WillOnce(Return(std::vector<std::string>({psuPath})));
265     EXPECT_CALL(mockedUtils, getService(_, StrEq(psuPath), _))
266         .WillOnce(Return(service));
267     EXPECT_CALL(mockedUtils, getVersion(StrEq(psuPath)))
268         .WillOnce(Return(std::string(version)));
269     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
270                                              _, StrEq(PRESENT)))
271         .WillOnce(Return(any(PropertyType(false)))); // not present
272 
273     // The item updater itself
274     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(dBusPath)))
275         .Times(1);
276 
277     // No activation/version objects are created
278     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath)))
279         .Times(0);
280     itemUpdater = std::make_unique<ItemUpdater>(mockedBus, dBusPath);
281 
282     // The PSU is present and version is added in a single call
283     Properties propAdded{{PRESENT, PropertyType(true)}};
284     EXPECT_CALL(mockedUtils, getVersion(StrEq(psuPath)))
285         .WillOnce(Return(std::string(version)));
286     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath)))
287         .Times(2);
288     onPsuInventoryChanged(psuPath, propAdded);
289 }
290 
291 TEST_F(TestItemUpdater, OnOnePSURemovedAndAdded)
292 {
293     constexpr auto psuPath = "/com/example/inventory/psu0";
294     constexpr auto service = "com.example.Software.Psu";
295     constexpr auto version = "version0";
296     std::string objPath = getObjPath(version);
297     EXPECT_CALL(mockedUtils, getPSUInventoryPath(_))
298         .WillOnce(Return(std::vector<std::string>({psuPath})));
299     EXPECT_CALL(mockedUtils, getService(_, StrEq(psuPath), _))
300         .WillOnce(Return(service));
301     EXPECT_CALL(mockedUtils, getVersion(StrEq(psuPath)))
302         .WillOnce(Return(std::string(version)));
303     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
304                                              _, StrEq(PRESENT)))
305         .WillOnce(Return(any(PropertyType(true)))); // present
306 
307     // The item updater itself
308     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(dBusPath)))
309         .Times(1);
310 
311     // activation and version object will be added
312     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath)))
313         .Times(2);
314     itemUpdater = std::make_unique<ItemUpdater>(mockedBus, dBusPath);
315 
316     // the activation and version object will be removed
317     Properties propRemoved{{PRESENT, PropertyType(false)}};
318     EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(objPath)))
319         .Times(2);
320     onPsuInventoryChanged(psuPath, propRemoved);
321 
322     Properties propAdded{{PRESENT, PropertyType(true)}};
323     EXPECT_CALL(mockedUtils, getVersion(StrEq(psuPath)))
324         .WillOnce(Return(std::string(version)));
325     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath)))
326         .Times(2);
327     onPsuInventoryChanged(psuPath, propAdded);
328 
329     // on exit, objects are removed
330     EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(objPath)))
331         .Times(2);
332     EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(dBusPath)))
333         .Times(1);
334 }
335 
336 TEST_F(TestItemUpdater,
337        TwoPSUsWithSameVersionRemovedAndAddedWithDifferntVersion)
338 {
339     constexpr auto psu0 = "/com/example/inventory/psu0";
340     constexpr auto psu1 = "/com/example/inventory/psu1";
341     constexpr auto service = "com.example.Software.Psu";
342     auto version0 = std::string("version0");
343     auto version1 = std::string("version0");
344     auto objPath0 = getObjPath(version0);
345     auto objPath1 = getObjPath(version1);
346 
347     EXPECT_CALL(mockedUtils, getPSUInventoryPath(_))
348         .WillOnce(Return(std::vector<std::string>({psu0, psu1})));
349     EXPECT_CALL(mockedUtils, getService(_, StrEq(psu0), _))
350         .WillOnce(Return(service));
351     EXPECT_CALL(mockedUtils, getService(_, StrEq(psu1), _))
352         .WillOnce(Return(service));
353     EXPECT_CALL(mockedUtils, getVersion(StrEq(psu0)))
354         .WillOnce(Return(std::string(version0)));
355     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu0), _,
356                                              StrEq(PRESENT)))
357         .WillOnce(Return(any(PropertyType(true)))); // present
358     EXPECT_CALL(mockedUtils, getVersion(StrEq(psu1)))
359         .WillOnce(Return(std::string(version1)));
360     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psu1), _,
361                                              StrEq(PRESENT)))
362         .WillOnce(Return(any(PropertyType(true)))); // present
363 
364     // The item updater itself
365     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(dBusPath)))
366         .Times(1);
367 
368     // activation and version object will be added
369     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath0)))
370         .Times(2);
371     itemUpdater = std::make_unique<ItemUpdater>(mockedBus, dBusPath);
372 
373     // Verify there is only one activation and it has two associations
374     const auto& activations = GetActivations();
375     EXPECT_EQ(1u, activations.size());
376     const auto& activation = activations.find(version0)->second;
377     auto assocs = activation->associations();
378     EXPECT_EQ(2u, assocs.size());
379     EXPECT_EQ(psu0, std::get<2>(assocs[0]));
380     EXPECT_EQ(psu1, std::get<2>(assocs[1]));
381 
382     // PSU0 is removed, only associations shall be updated
383     Properties propRemoved{{PRESENT, PropertyType(false)}};
384     onPsuInventoryChanged(psu0, propRemoved);
385     assocs = activation->associations();
386     EXPECT_EQ(1u, assocs.size());
387     EXPECT_EQ(psu1, std::get<2>(assocs[0]));
388 
389     // PSU1 is removed, the activation and version object shall be removed
390     EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(objPath0)))
391         .Times(2);
392     onPsuInventoryChanged(psu1, propRemoved);
393 
394     // Add PSU0 and PSU1 back, but PSU1 with a different version
395     version1 = "version1";
396     objPath1 = getObjPath(version1);
397     Properties propAdded0{{PRESENT, PropertyType(true)}};
398     Properties propAdded1{{PRESENT, PropertyType(true)}};
399     EXPECT_CALL(mockedUtils, getVersion(StrEq(psu0)))
400         .WillOnce(Return(std::string(version0)));
401     EXPECT_CALL(mockedUtils, getVersion(StrEq(psu1)))
402         .WillOnce(Return(std::string(version1)));
403     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath0)))
404         .Times(2);
405     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath1)))
406         .Times(2);
407     onPsuInventoryChanged(psu0, propAdded0);
408     onPsuInventoryChanged(psu1, propAdded1);
409 
410     // on exit, objects are removed
411     EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(objPath0)))
412         .Times(2);
413     EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(objPath1)))
414         .Times(2);
415     EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(dBusPath)))
416         .Times(1);
417 }
418 
419 TEST_F(TestItemUpdater, scanDirOnNoPSU)
420 {
421     constexpr auto psuPath = "/com/example/inventory/psu0";
422     constexpr auto service = "com.example.Software.Psu";
423     constexpr auto version = "version0";
424     std::string objPath = getObjPath(version);
425     EXPECT_CALL(mockedUtils, getPSUInventoryPath(_))
426         .WillOnce(Return(std::vector<std::string>({psuPath})));
427     EXPECT_CALL(mockedUtils, getService(_, StrEq(psuPath), _))
428         .WillOnce(Return(service));
429     EXPECT_CALL(mockedUtils, getVersion(StrEq(psuPath)))
430         .WillOnce(Return(std::string(version)));
431     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
432                                              _, StrEq(PRESENT)))
433         .WillOnce(Return(any(PropertyType(false)))); // not present
434 
435     // The item updater itself
436     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(dBusPath)))
437         .Times(1);
438 
439     // No activation/version objects are created
440     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath)))
441         .Times(0);
442     itemUpdater = std::make_unique<ItemUpdater>(mockedBus, dBusPath);
443 
444     // The valid image in test/psu-images-one-valid-one-invalid/model-1/
445     auto objPathValid = getObjPath("psu-test.v0.4");
446     auto objPathInvalid = getObjPath("psu-test.v0.5");
447     // activation and version object will be added on scan dir
448     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPathValid)))
449         .Times(2);
450     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPathInvalid)))
451         .Times(0);
452     scanDirectory("./psu-images-one-valid-one-invalid");
453 }
454 
455 TEST_F(TestItemUpdater, scanDirOnSamePSUVersion)
456 {
457     constexpr auto psuPath = "/com/example/inventory/psu0";
458     constexpr auto service = "com.example.Software.Psu";
459     constexpr auto version = "version0";
460     std::string objPath = getObjPath(version);
461     EXPECT_CALL(mockedUtils, getPSUInventoryPath(_))
462         .WillOnce(Return(std::vector<std::string>({psuPath})));
463     EXPECT_CALL(mockedUtils, getService(_, StrEq(psuPath), _))
464         .WillOnce(Return(service));
465     EXPECT_CALL(mockedUtils, getVersion(StrEq(psuPath)))
466         .WillOnce(Return(std::string(version)));
467     EXPECT_CALL(mockedUtils, getPropertyImpl(_, StrEq(service), StrEq(psuPath),
468                                              _, StrEq(PRESENT)))
469         .WillOnce(Return(any(PropertyType(true)))); // present
470 
471     // The item updater itself
472     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(dBusPath)))
473         .Times(1);
474 
475     // activation and version object will be added
476     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath)))
477         .Times(2);
478     itemUpdater = std::make_unique<ItemUpdater>(mockedBus, dBusPath);
479 
480     // The valid image in test/psu-images-valid-version0/model-3/ has the same
481     // version as the running PSU, so no objects will be created, but only the
482     // path will be set to the version object
483     EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath)))
484         .Times(0);
485     EXPECT_CALL(sdbusMock, sd_bus_emit_properties_changed_strv(
486                                _, StrEq(objPath),
487                                StrEq("xyz.openbmc_project.Common.FilePath"),
488                                Pointee(StrEq("Path"))))
489         .Times(1);
490     scanDirectory("./psu-images-valid-version0");
491 }
492