You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test.py 22 KiB

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. # -*- coding: utf-8 -*-
  2. import nilmtools.copy_one
  3. import nilmtools.cleanup
  4. import nilmtools.copy_one
  5. import nilmtools.copy_wildcard
  6. import nilmtools.decimate_auto
  7. import nilmtools.decimate
  8. import nilmtools.insert
  9. import nilmtools.median
  10. import nilmtools.pipewatch
  11. import nilmtools.prep
  12. import nilmtools.sinefit
  13. import nilmtools.trainola
  14. from nilmdb.utils.interval import Interval
  15. from nose.tools import assert_raises
  16. import unittest
  17. import math
  18. from testutil.helpers import *
  19. import multiprocessing
  20. import traceback
  21. from urllib.request import urlopen
  22. from nilmtools.filter import ArgumentError
  23. def run_cherrypy_server(path, port, event):
  24. db = nilmdb.utils.serializer_proxy(nilmdb.server.NilmDB)(path)
  25. server = nilmdb.server.Server(db, host="127.0.0.1",
  26. port=port, stoppable=True)
  27. server.start(blocking = True, event = event)
  28. db.close()
  29. class CommandTester():
  30. url = "http://localhost:32182/"
  31. url2 = "http://localhost:32183/"
  32. @classmethod
  33. def setup_class(cls):
  34. # Use multiprocessing with "spawn" method, so that we can
  35. # start two fully independent cherrypy instances
  36. # (needed for copy-wildcard)
  37. multiprocessing.set_start_method('spawn')
  38. events = []
  39. for (path, port) in (("tests/testdb1", 32182),
  40. ("tests/testdb2", 32183)):
  41. recursive_unlink(path)
  42. event = multiprocessing.Event()
  43. proc = multiprocessing.Process(target=run_cherrypy_server,
  44. args=(path, port, event))
  45. proc.start()
  46. events.append(event)
  47. for event in events:
  48. if not event.wait(timeout = 10):
  49. raise AssertionError("server didn't start")
  50. @classmethod
  51. def teardown_class(cls):
  52. urlopen("http://127.0.0.1:32182/exit/", timeout = 1)
  53. urlopen("http://127.0.0.1:32183/exit/", timeout = 1)
  54. def run(self, arg_string, infile=None, outfile=None):
  55. """Run a cmdline client with the specified argument string,
  56. passing the given input. Save the output and exit code."""
  57. os.environ['NILMDB_URL'] = self.url
  58. self.last_args = arg_string
  59. class stdio_wrapper:
  60. def __init__(self, stdin, stdout, stderr):
  61. self.io = (stdin, stdout, stderr)
  62. def __enter__(self):
  63. self.saved = ( sys.stdin, sys.stdout, sys.stderr )
  64. ( sys.stdin, sys.stdout, sys.stderr ) = self.io
  65. def __exit__(self, type, value, traceback):
  66. ( sys.stdin, sys.stdout, sys.stderr ) = self.saved
  67. # Empty input if none provided
  68. if infile is None:
  69. infile = io.TextIOWrapper(io.BytesIO(b""))
  70. # Capture stderr
  71. errfile = io.TextIOWrapper(io.BytesIO())
  72. if outfile is None:
  73. # If no output file, capture stdout with stderr
  74. outfile = errfile
  75. with stdio_wrapper(infile, outfile, errfile) as s:
  76. try:
  77. args = shlex.split(arg_string)
  78. sys.argv[0] = "test_runner"
  79. self.main(args)
  80. sys.exit(0)
  81. except SystemExit as e:
  82. exitcode = e.code
  83. except Exception as e:
  84. traceback.print_exc()
  85. exitcode = 1
  86. # Capture raw binary output, and also try to decode a Unicode
  87. # string copy.
  88. self.captured_binary = outfile.buffer.getvalue()
  89. try:
  90. outfile.seek(0)
  91. self.captured = outfile.read()
  92. except UnicodeDecodeError:
  93. self.captured = None
  94. self.exitcode = exitcode
  95. def ok(self, arg_string, infile = None):
  96. self.run(arg_string, infile)
  97. if self.exitcode != 0:
  98. self.dump()
  99. eq_(self.exitcode, 0)
  100. def fail(self, arg_string, infile=None, exitcode=None):
  101. self.run(arg_string, infile)
  102. if exitcode is not None and self.exitcode != exitcode:
  103. # Wrong exit code
  104. self.dump()
  105. eq_(self.exitcode, exitcode)
  106. if self.exitcode == 0:
  107. # Success, when we wanted failure
  108. self.dump()
  109. ne_(self.exitcode, 0)
  110. def contain(self, checkstring, contain=True):
  111. if contain:
  112. in_(checkstring, self.captured)
  113. else:
  114. nin_(checkstring, self.captured)
  115. def match(self, checkstring):
  116. eq_(checkstring, self.captured)
  117. def matchfile(self, file):
  118. # Captured data should match file contents exactly
  119. with open(file) as f:
  120. contents = f.read()
  121. if contents != self.captured:
  122. print("--- reference file (first 1000 bytes):\n")
  123. print(contents[0:1000] + "\n")
  124. print("--- captured data (first 1000 bytes):\n")
  125. print(self.captured[0:1000] + "\n")
  126. zipped = itertools.zip_longest(contents, self.captured)
  127. for (n, (a, b)) in enumerate(zipped):
  128. if a != b:
  129. print("--- first difference is at offset", n)
  130. print("--- reference:", repr(a))
  131. print("--- captured:", repr(b))
  132. break
  133. raise AssertionError("captured data doesn't match " + file)
  134. def matchfilecount(self, file):
  135. # Last line of captured data should match the number of
  136. # non-commented lines in file
  137. count = 0
  138. with open(file) as f:
  139. for line in f:
  140. if line[0] != '#':
  141. count += 1
  142. eq_(self.captured.splitlines()[-1], sprintf("%d", count))
  143. def dump(self):
  144. printf("\n===args start===\n%s\n===args end===\n", self.last_args)
  145. printf("===dump start===\n%s===dump end===\n", self.captured)
  146. class TestAllCommands(CommandTester):
  147. def test_00_load_data(self):
  148. client = nilmdb.client.Client(url=self.url)
  149. client.stream_create("/newton/prep", "float32_8")
  150. client.stream_set_metadata("/newton/prep",
  151. { "description": "newton" })
  152. for ts in ("20120323T1000", "20120323T1002", "20120323T1004"):
  153. start = nilmdb.utils.time.parse_time(ts)
  154. fn = f"tests/data/prep-{ts}"
  155. data = nilmdb.utils.timestamper.TimestamperRate(fn, start, 120)
  156. client.stream_insert("/newton/prep", data);
  157. def test_01_copy(self):
  158. self.main = nilmtools.copy_one.main
  159. client = nilmdb.client.Client(url=self.url)
  160. # basic arguments
  161. self.fail(f"")
  162. self.fail(f"no-such-src no-such-dest")
  163. self.contain("source path no-such-src not found")
  164. self.fail(f"-u {self.url} no-such-src no-such-dest")
  165. # nonexistent dest
  166. self.fail(f"/newton/prep /newton/prep-copy")
  167. self.contain("Destination /newton/prep-copy doesn't exist")
  168. # wrong type
  169. client.stream_create("/newton/prep-copy-wrongtype", "uint16_6")
  170. self.fail(f"/newton/prep /newton/prep-copy-wrongtype")
  171. self.contain("wrong number of fields")
  172. # copy with metadata, and compare
  173. client.stream_create("/newton/prep-copy", "float32_8")
  174. self.ok(f"/newton/prep /newton/prep-copy")
  175. a = list(client.stream_extract("/newton/prep"))
  176. b = list(client.stream_extract("/newton/prep-copy"))
  177. eq_(a, b)
  178. a = client.stream_get_metadata("/newton/prep")
  179. b = client.stream_get_metadata("/newton/prep-copy")
  180. eq_(a, b)
  181. # copy with no metadata
  182. client.stream_create("/newton/prep-copy-nometa", "float32_8")
  183. self.ok(f"--nometa /newton/prep /newton/prep-copy-nometa")
  184. a = list(client.stream_extract("/newton/prep"))
  185. b = list(client.stream_extract("/newton/prep-copy-nometa"))
  186. eq_(a, b)
  187. a = client.stream_get_metadata("/newton/prep")
  188. b = client.stream_get_metadata("/newton/prep-copy-nometa")
  189. ne_(a, b)
  190. def test_02_copy_wildcard(self):
  191. self.main = nilmtools.copy_wildcard.main
  192. client1 = nilmdb.client.Client(url=self.url)
  193. client2 = nilmdb.client.Client(url=self.url2)
  194. # basic arguments
  195. self.fail(f"")
  196. self.fail(f"/newton")
  197. self.fail(f"-u {self.url} -U {self.url} /newton")
  198. self.contain("URL must be different")
  199. # no matches; silent
  200. self.ok(f"-u {self.url} -U {self.url2} /newton")
  201. self.ok(f"-u {self.url} -U {self.url2} /asdf*")
  202. self.ok(f"-u {self.url2} -U {self.url} /newton*")
  203. eq_(client2.stream_list(), [])
  204. # this won't actually copy, but will still create streams
  205. self.ok(f"-u {self.url} -U {self.url2} --dry-run /newton*")
  206. self.contain("Creating destination stream /newton/prep-copy")
  207. eq_(len(list(client2.stream_extract("/newton/prep"))), 0)
  208. # this should copy a bunch
  209. self.ok(f"-u {self.url} -U {self.url2} /*")
  210. self.contain("Creating destination stream /newton/prep-copy", False)
  211. eq_(client1.stream_list(), client2.stream_list())
  212. eq_(list(client1.stream_extract("/newton/prep")),
  213. list(client2.stream_extract("/newton/prep")))
  214. eq_(client1.stream_get_metadata("/newton/prep"),
  215. client2.stream_get_metadata("/newton/prep"))
  216. # repeating it is OK; it just won't recreate streams.
  217. # Let's try with --nometa too
  218. client2.stream_remove("/newton/prep")
  219. client2.stream_destroy("/newton/prep")
  220. self.ok(f"-u {self.url} -U {self.url2} --nometa /newton*")
  221. self.contain("Creating destination stream /newton/prep-copy", False)
  222. self.contain("Creating destination stream /newton/prep", True)
  223. eq_(client1.stream_list(), client2.stream_list())
  224. eq_(list(client1.stream_extract("/newton/prep")),
  225. list(client2.stream_extract("/newton/prep")))
  226. eq_(client2.stream_get_metadata("/newton/prep"), {})
  227. # fill in test cases
  228. self.ok(f"-u {self.url} -U {self.url2} -s 2010 -e 2020 -F /newton*")
  229. def test_03_decimate(self):
  230. self.main = nilmtools.decimate.main
  231. client = nilmdb.client.Client(url=self.url)
  232. # basic arguments
  233. self.fail(f"")
  234. # no dest
  235. self.fail(f"/newton/prep /newton/prep-decimated-1")
  236. self.contain("doesn't exist")
  237. # wrong dest shape
  238. client.stream_create("/newton/prep-decimated-bad", "float32_8")
  239. self.fail(f"/newton/prep /newton/prep-decimated-bad")
  240. self.contain("wrong number of fields")
  241. # bad factor
  242. self.fail(f"/newton/prep -f 1 /newton/prep-decimated-bad")
  243. self.contain("needs to be 2 or more")
  244. # ok, default factor 4
  245. client.stream_create("/newton/prep-decimated-4", "float32_24")
  246. self.ok(f"/newton/prep /newton/prep-decimated-4")
  247. a = client.stream_count("/newton/prep")
  248. b = client.stream_count("/newton/prep-decimated-4")
  249. eq_(a // 4, b)
  250. # factor 10
  251. client.stream_create("/newton/prep-decimated-10", "float32_24")
  252. self.ok(f"/newton/prep -f 10 /newton/prep-decimated-10")
  253. self.contain("Processing")
  254. a = client.stream_count("/newton/prep")
  255. b = client.stream_count("/newton/prep-decimated-10")
  256. eq_(a // 10, b)
  257. # different factor, same target
  258. self.fail(f"/newton/prep -f 16 /newton/prep-decimated-10")
  259. self.contain("Metadata in destination stream")
  260. self.contain("decimate_factor = 10")
  261. self.contain("doesn't match desired data")
  262. self.contain("decimate_factor = 16")
  263. # unless we force it
  264. self.ok(f"/newton/prep -f 16 -F /newton/prep-decimated-10")
  265. a = client.stream_count("/newton/prep")
  266. b = client.stream_count("/newton/prep-decimated-10")
  267. # but all data was already converted, so no more
  268. eq_(a // 10, b)
  269. # if we try to decimate an already-decimated stream, the suggested
  270. # shape is different
  271. self.fail(f"/newton/prep-decimated-4 -f 4 /newton/prep-decimated-16")
  272. self.contain("create /newton/prep-decimated-16 float32_24")
  273. # decimate again
  274. client.stream_create("/newton/prep-decimated-16", "float32_24")
  275. self.ok(f"/newton/prep-decimated-4 -f 4 /newton/prep-decimated-16")
  276. self.contain("Processing")
  277. # check shape suggestion for different input types
  278. for (shape, expected) in (("int32_1", "float64_3"),
  279. ("uint32_1", "float64_3"),
  280. ("int64_1", "float64_3"),
  281. ("uint64_1", "float64_3"),
  282. ("float32_1", "float32_3"),
  283. ("float64_1", "float64_3")):
  284. client.stream_create(f"/test/{shape}", shape)
  285. self.fail(f"/test/{shape} /test/{shape}-decim")
  286. self.contain(f"create /test/{shape}-decim {expected}")
  287. def test_04_decimate_auto(self):
  288. self.main = nilmtools.decimate_auto.main
  289. client = nilmdb.client.Client(url=self.url)
  290. self.fail(f"")
  291. self.fail(f"--max -1 asdf")
  292. self.contain("bad max")
  293. self.fail(f"/no/such/stream")
  294. self.contain("no stream matched path")
  295. # normal run
  296. self.ok(f"/newton/prep")
  297. # can't auto decimate a decimated stream
  298. self.fail(f"/newton/prep-decimated-16")
  299. self.contain("need to pass the base stream instead")
  300. # decimate prep again, this time much more; also use -F
  301. self.ok(f"-m 10 --force-metadata /newton/pr??")
  302. self.contain("Level 4096 decimation has 9 rows")
  303. # decimate the different shapes
  304. self.ok(f"/test/*")
  305. self.contain("Level 1 decimation has 0 rows")
  306. def test_05_insert(self):
  307. self.main = nilmtools.insert.main
  308. client = nilmdb.client.Client(url=self.url)
  309. self.fail(f"")
  310. self.ok(f"--help")
  311. # mutually exclusive arguments
  312. self.fail(f"--delta --rate 123 /foo bar")
  313. self.fail(f"--live --filename /foo bar")
  314. # Insert from file
  315. client.stream_create("/insert/prep", "float32_8")
  316. t0 = "tests/data/prep-20120323T1000"
  317. t2 = "tests/data/prep-20120323T1002"
  318. t4 = "tests/data/prep-20120323T1004"
  319. self.ok(f"--file --dry-run --rate 120 /insert/prep {t0} {t2} {t4}")
  320. self.contain("Dry run")
  321. # wrong rate
  322. self.fail(f"--file --dry-run --rate 10 /insert/prep {t0} {t2} {t4}")
  323. self.contain("Data is coming in too fast")
  324. # skip forward in time
  325. self.ok(f"--file --dry-run --rate 120 /insert/prep {t0} {t4}")
  326. self.contain("data timestamp behind by 120")
  327. self.contain("Skipping data timestamp forward")
  328. # skip backwards in time
  329. self.fail(f"--file --dry-run --rate 120 /insert/prep {t0} {t2} {t0}")
  330. self.contain("data timestamp ahead by 240")
  331. # skip backwards in time is OK if --skip provided
  332. self.ok(f"--skip -f -D -r 120 insert/prep {t0} {t2} {t0} {t4}")
  333. self.contain("Skipping the remainder of this file")
  334. # Now insert for real
  335. self.ok(f"--skip --file --rate 120 /insert/prep {t0} {t2} {t4}")
  336. self.contain("Done")
  337. # Overlap
  338. self.fail(f"--skip --file --rate 120 /insert/prep {t0}")
  339. self.contain("new data overlaps existing data")
  340. # Not overlap if we change file offset
  341. self.ok(f"--skip --file --rate 120 -o 0 /insert/prep {t0}")
  342. # Data with no timestamp
  343. self.fail(f"-f -r 120 /insert/prep tests/data/prep-notime")
  344. self.contain("No idea what timestamp to use")
  345. # Check intervals so far
  346. eq_(list(client.stream_intervals("/insert/prep")),
  347. [[1332507600000000, 1332507959991668],
  348. [1332511200000000, 1332511319991668]])
  349. # Delta supplied by file
  350. self.ok(f"--file --delta -o 0 /insert/prep {t4}-delta")
  351. eq_(list(client.stream_intervals("/insert/prep")),
  352. [[1332507600000000, 1332507959991668],
  353. [1332511200000000, 1332511319991668],
  354. [1332511440000000, 1332511499000001]])
  355. # Now fake live timestamps by using the delta file, and a
  356. # fake clock that increments one second per call.
  357. def fake_time_now():
  358. nonlocal fake_time_base
  359. ret = fake_time_base
  360. fake_time_base += 1000000
  361. return ret
  362. real_time_now = nilmtools.insert.time_now
  363. nilmtools.insert.time_now = fake_time_now
  364. # Delta supplied by file. This data is too fast because delta
  365. # contains a 50 sec jump
  366. fake_time_base = 1332511560000000
  367. self.fail(f"--live --delta -o 0 /insert/prep {t4}-delta")
  368. self.contain("Data is coming in too fast")
  369. self.contain("data time is Fri, 23 Mar 2012 10:06:55")
  370. self.contain("clock time is only Fri, 23 Mar 2012 10:06:06")
  371. # This data is OK, no jump
  372. fake_time_base = 1332511560000000
  373. self.ok(f"--live --delta -o 0 /insert/prep {t4}-delta2")
  374. # This has unparseable delta
  375. fake_time_base = 1332511560000000
  376. self.fail(f"--live --delta -o 0 /insert/prep {t4}-delta3")
  377. self.contain("can't parse delta")
  378. # Insert some gzipped data, with no timestamp in name
  379. bp1 = "tests/data/bpnilm-raw-1.gz"
  380. bp2 = "tests/data/bpnilm-raw-2.gz"
  381. client.stream_create("/insert/raw", "uint16_6")
  382. self.ok(f"--file /insert/raw {bp1} {bp2}")
  383. # Try truncated data
  384. tr = "tests/data/trunc"
  385. self.ok(f"--file /insert/raw {tr}1 {tr}2 {tr}3 {tr}4")
  386. nilmtools.insert.time_now = real_time_now
  387. def test_06_sinefit(self):
  388. self.main = nilmtools.sinefit.main
  389. client = nilmdb.client.Client(url=self.url)
  390. self.fail(f"")
  391. self.ok(f"--help")
  392. # generate raw data
  393. data_sec = 50
  394. client.stream_create("/sf/raw", "uint16_2")
  395. with client.stream_insert_context("/sf/raw") as ctx:
  396. fs = 8000
  397. freq = 60.0
  398. for n in range(fs * data_sec):
  399. t = n / fs
  400. v = math.sin(t * 2 * math.pi * freq)
  401. i = 0.3 * math.sin(3*t) + math.sin(t)
  402. line = b"%d %d %d\n" % (
  403. (t + 1234567890) * 1e6,
  404. v * 32767 + 32768,
  405. i * 32768 + 32768)
  406. ctx.insert(line)
  407. if 0:
  408. for (s, e) in client.stream_intervals("/sf/raw"):
  409. print(Interval(s,e).human_string())
  410. client.stream_create("/sf/out-bad", "float32_4")
  411. self.fail(f"--column 1 /sf/raw /sf/out-bad")
  412. self.contain("wrong number of fields")
  413. self.fail(f"--column 1 /sf/raw /sf/out")
  414. self.contain("/sf/out doesn't exist")
  415. # basic run
  416. client.stream_create("/sf/out", "float32_3")
  417. self.ok(f"--column 1 /sf/raw /sf/out")
  418. eq_(client.stream_count("/sf/out"), 60 * data_sec)
  419. # parameter errors
  420. self.fail(f"--column 0 /sf/raw /sf/out")
  421. self.contain("need a column number")
  422. self.fail(f"/sf/raw /sf/out")
  423. self.contain("need a column number")
  424. self.fail(f"-c 1 --frequency 0 /sf/raw /sf/out")
  425. self.contain("frequency must be")
  426. self.fail(f"-c 1 --min-freq 100 /sf/raw /sf/out")
  427. self.contain("invalid min or max frequency")
  428. self.fail(f"-c 1 --max-freq 5 /sf/raw /sf/out")
  429. self.contain("invalid min or max frequency")
  430. self.fail(f"-c 1 --min-amp -1 /sf/raw /sf/out")
  431. self.contain("min amplitude must be")
  432. # trigger some warnings
  433. client.stream_create("/sf/out2", "float32_3")
  434. self.ok(f"-c 1 -f 500 -e @1234567897000000 /sf/raw /sf/out2")
  435. self.contain("outside valid range")
  436. self.contain("1000 warnings suppressed")
  437. eq_(client.stream_count("/sf/out2"), 0)
  438. self.ok(f"-c 1 -a 40000 -e @1234567898000000 /sf/raw /sf/out2")
  439. self.contain("below minimum threshold")
  440. # get coverage for "advance = N/2" line near end of sinefit,
  441. # where we found a fit but it was after the end of the window,
  442. # so we didn't actually mark anything in this window.
  443. self.ok(f"-c 1 -f 240 -m 50 -e @1234567898010000 /sf/raw /sf/out2")
  444. def test_07_median(self):
  445. self.main = nilmtools.median.main
  446. client = nilmdb.client.Client(url=self.url)
  447. self.fail(f"")
  448. self.ok(f"--help")
  449. client.stream_create("/median/1", "float32_8")
  450. client.stream_create("/median/2", "float32_8")
  451. self.fail("/newton/prep /median/0")
  452. self.contain("doesn't exist")
  453. self.ok("/newton/prep /median/1")
  454. self.ok("--difference /newton/prep /median/2")
  455. def test_08_cleanup(self):
  456. self.main = nilmtools.cleanup.main
  457. client = nilmdb.client.Client(url=self.url)
  458. # This mostly just gets coverage, doesn't carefully verify behavior
  459. self.fail(f"")
  460. self.ok(f"--help")
  461. self.fail(f"tests/data/cleanup-bad.cfg")
  462. self.contain("unknown units")
  463. client.stream_create("/empty/foo", "uint16_1")
  464. self.ok(f"tests/data/cleanup.cfg")
  465. self.contain("'/nonexistent/bar' did not match any existing streams")
  466. self.contain("no config for existing stream '/empty/foo'")
  467. self.contain("nothing to do (only 0.00 weeks of data present)")
  468. self.contain("specify --yes to actually perform")
  469. self.ok(f"--yes tests/data/cleanup.cfg")
  470. self.contain("removing data before")
  471. self.contain("removing from /sf/raw")
  472. self.ok(f"--estimate tests/data/cleanup.cfg")
  473. self.contain("Total estimated disk usage")
  474. self.contain("MiB")
  475. self.contain("GiB")
  476. self.ok(f"--yes tests/data/cleanup-nodecim.cfg")
  477. self.ok(f"--estimate tests/data/cleanup-nodecim.cfg")
  478. def test_09_trainola(self):
  479. self.main = nilmtools.trainola.main
  480. def test_10_pipewatch(self):
  481. self.main = nilmtools.pipewatch.main
  482. def test_11_prep(self):
  483. self.main = nilmtools.prep.main