alibuild 1.17.19__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. alibuild-1.17.19.data/scripts/aliBuild +137 -0
  2. alibuild-1.17.19.data/scripts/aliDeps +7 -0
  3. alibuild-1.17.19.data/scripts/aliDoctor +7 -0
  4. alibuild-1.17.19.data/scripts/alienv +344 -0
  5. alibuild-1.17.19.data/scripts/pb +7 -0
  6. alibuild-1.17.19.dist-info/METADATA +78 -0
  7. alibuild-1.17.19.dist-info/RECORD +74 -0
  8. alibuild-1.17.19.dist-info/WHEEL +5 -0
  9. alibuild-1.17.19.dist-info/licenses/LICENSE.md +674 -0
  10. alibuild-1.17.19.dist-info/top_level.txt +5 -0
  11. alibuild_helpers/__init__.py +21 -0
  12. alibuild_helpers/_version.py +21 -0
  13. alibuild_helpers/analytics.py +120 -0
  14. alibuild_helpers/args.py +493 -0
  15. alibuild_helpers/build.py +1209 -0
  16. alibuild_helpers/build_template.sh +314 -0
  17. alibuild_helpers/clean.py +83 -0
  18. alibuild_helpers/cmd.py +154 -0
  19. alibuild_helpers/deps.py +116 -0
  20. alibuild_helpers/doctor.py +195 -0
  21. alibuild_helpers/git.py +104 -0
  22. alibuild_helpers/init.py +103 -0
  23. alibuild_helpers/log.py +132 -0
  24. alibuild_helpers/scm.py +31 -0
  25. alibuild_helpers/sl.py +62 -0
  26. alibuild_helpers/sync.py +693 -0
  27. alibuild_helpers/templating_plugin.py +18 -0
  28. alibuild_helpers/utilities.py +662 -0
  29. alibuild_helpers/workarea.py +179 -0
  30. debian/changelog +11 -0
  31. debian/compat +1 -0
  32. debian/control +14 -0
  33. debian/copyright +10 -0
  34. debian/files +1 -0
  35. debian/rules +7 -0
  36. docs/README.md +1 -0
  37. docs/SUPPORT +3 -0
  38. docs/docs/alice_logo.png +0 -0
  39. docs/docs/deps.png +0 -0
  40. docs/docs/index.md +75 -0
  41. docs/docs/quick.md +89 -0
  42. docs/docs/reference.md +430 -0
  43. docs/docs/stylesheets/extra.css +9 -0
  44. docs/docs/troubleshooting.md +346 -0
  45. docs/docs/user.md +413 -0
  46. docs/mkdocs.yml +37 -0
  47. templates/alibuild_to_please.jnj +63 -0
  48. tests/test_analytics.py +42 -0
  49. tests/test_args.py +119 -0
  50. tests/test_build.py +426 -0
  51. tests/test_clean.py +154 -0
  52. tests/test_cmd.py +73 -0
  53. tests/test_deps.py +79 -0
  54. tests/test_doctor.py +128 -0
  55. tests/test_git.py +48 -0
  56. tests/test_hashing.py +67 -0
  57. tests/test_init.py +103 -0
  58. tests/test_log.py +50 -0
  59. tests/test_packagelist.py +235 -0
  60. tests/test_parseRecipe.py +132 -0
  61. tests/test_sync.py +332 -0
  62. tests/test_utilities.py +383 -0
  63. tests/test_workarea.py +101 -0
  64. tests/testdist/broken1.sh +1 -0
  65. tests/testdist/broken2.sh +1 -0
  66. tests/testdist/broken3.sh +3 -0
  67. tests/testdist/broken4.sh +2 -0
  68. tests/testdist/broken5.sh +2 -0
  69. tests/testdist/broken6.sh +2 -0
  70. tests/testdist/broken7.sh +5 -0
  71. tests/testdist/clobber-initdotsh.sh +4 -0
  72. tests/testdist/defaults-o2.sh +10 -0
  73. tests/testdist/delete-etc.sh +4 -0
  74. tests/testdist/tracking-env.sh +6 -0
tests/test_deps.py ADDED
@@ -0,0 +1,79 @@
1
+ from unittest.mock import patch, MagicMock
2
+ from io import StringIO
3
+ import os.path
4
+
5
+ from alibuild_helpers.deps import doDeps
6
+ from argparse import Namespace
7
+ import unittest
8
+
9
+ RECIPES = {
10
+ "/dist/defaults-release.sh": """\
11
+ package: defaults-release
12
+ version: v1
13
+ ---
14
+ """,
15
+ "/dist/gcc-toolchain.sh": """\
16
+ package: GCC-Toolchain
17
+ version: v1
18
+ ---
19
+ """,
20
+ "/dist/aliroot.sh": """\
21
+ package: AliRoot
22
+ version: v1
23
+ requires:
24
+ - ROOT
25
+ - GCC-Toolchain
26
+ ---
27
+ """,
28
+ "/dist/root.sh": """\
29
+ package: ROOT
30
+ version: v1
31
+ build_requires:
32
+ - GCC-Toolchain
33
+ ---
34
+ """,
35
+ }
36
+
37
+
38
+ class DepsTestCase(unittest.TestCase):
39
+
40
+ @patch("alibuild_helpers.deps.open")
41
+ @patch("alibuild_helpers.deps.execute", new=lambda cmd: True)
42
+ @patch("alibuild_helpers.utilities.open", new=lambda f: StringIO(RECIPES[f]))
43
+ def test_deps(self, mockDepsOpen):
44
+ """Check doDeps doesn't raise an exception."""
45
+ dot = StringIO()
46
+ dot.name = ""
47
+
48
+ def depsOpen(fn, mode):
49
+ dot.name = fn
50
+ return dot
51
+ mockDepsOpen.side_effect = depsOpen
52
+
53
+ args = Namespace(workDir="/work",
54
+ configDir="/dist",
55
+ debug=False,
56
+ docker=False,
57
+ dockerImage=None,
58
+ docker_extra_args=["--network=host"],
59
+ preferSystem=[],
60
+ noSystem="*",
61
+ architecture="slc7_x86-64",
62
+ disable=[],
63
+ neat=True,
64
+ outdot="/tmp/out.dot",
65
+ outgraph="/tmp/outgraph.pdf",
66
+ package="AliRoot",
67
+ defaults="release")
68
+ def fake_exists(n):
69
+ return {"/alidist/aliroot.sh": True}
70
+ with patch.object(os.path, "exists", fake_exists):
71
+ doDeps(args, MagicMock())
72
+
73
+ # Same check without explicit intermediate dotfile
74
+ args.outdot = None
75
+ doDeps(args, MagicMock())
76
+
77
+
78
+ if __name__ == '__main__':
79
+ unittest.main()
tests/test_doctor.py ADDED
@@ -0,0 +1,128 @@
1
+ from unittest.mock import patch, MagicMock
2
+ from io import StringIO
3
+ import os.path
4
+
5
+ from alibuild_helpers.doctor import doDoctor
6
+ from argparse import Namespace
7
+ import unittest
8
+
9
+ RECIPE_DEFAULTS_RELEASE = """package: defaults-release
10
+ version: v1
11
+ ---
12
+ """
13
+
14
+ RECIPE_PACKAGE1 = """package: Package1
15
+ version: v1
16
+ prefer_system: .*
17
+ prefer_system_check: /bin/false
18
+ ---
19
+ """
20
+
21
+ RECIPE_SYSDEP = """package: SysDep
22
+ version: v1
23
+ system_requirement: .*
24
+ system_requirement_check: /bin/false
25
+ ---
26
+ """
27
+
28
+ RECIPE_BREAKDEFAULTS = """package: BreakDefaults
29
+ version: v1
30
+ valid_defaults:
31
+ - its_not_there
32
+ ---
33
+ """
34
+
35
+ RECIPE_TESTDEF1 = """package: TestDef1
36
+ version: v1
37
+ valid_defaults:
38
+ - common_default
39
+ - default1
40
+ requires:
41
+ - TestDef2
42
+ ---
43
+ """
44
+
45
+ RECIPE_TESTDEF2 = """package: TestDef2
46
+ version: v1
47
+ valid_defaults:
48
+ - common_default
49
+ - default2
50
+ ---
51
+ """
52
+ class DoctorTestCase(unittest.TestCase):
53
+
54
+ @patch("alibuild_helpers.doctor.banner")
55
+ @patch("alibuild_helpers.doctor.warning")
56
+ @patch("alibuild_helpers.doctor.error")
57
+ @patch("alibuild_helpers.doctor.exists")
58
+ @patch("alibuild_helpers.utilities.open")
59
+ def test_doctor(self, mockOpen, mockExists, mockPrintError, mockPrintWarning, mockPrintBanner):
60
+ mockExists.return_value = True
61
+ mockOpen.side_effect = lambda f: { "/dist/package1.sh" : StringIO(RECIPE_PACKAGE1),
62
+ "/dist/testdef1.sh" : StringIO(RECIPE_TESTDEF1),
63
+ "/dist/testdef2.sh" : StringIO(RECIPE_TESTDEF2),
64
+ "/dist/sysdep.sh" : StringIO(RECIPE_SYSDEP),
65
+ "/dist/defaults-release.sh" : StringIO(RECIPE_DEFAULTS_RELEASE),
66
+ "/dist/breakdefaults.sh" : StringIO(RECIPE_BREAKDEFAULTS) }[f]
67
+
68
+ # Collect printouts
69
+ def resetOut():
70
+ return { "warning": StringIO(), "error": StringIO(), "banner": StringIO() }
71
+ mockPrintError.side_effect = lambda e, *a: out["error"].write((e%a)+"\n")
72
+ mockPrintWarning.side_effect = lambda e, *a: out["warning"].write((e%a)+"\n")
73
+ mockPrintBanner.side_effect = lambda e, *a: out["banner"].write((e%a)+"\n")
74
+
75
+ args = Namespace(workDir="/work",
76
+ configDir="/dist",
77
+ docker=False,
78
+ dockerImage=None,
79
+ docker_extra_args=["--network=host"],
80
+ debug=False,
81
+ preferSystem=[],
82
+ noSystem="*",
83
+ architecture="osx_x86-64",
84
+ disable=[],
85
+ defaults="release")
86
+
87
+ def fake_exists(n):
88
+ return {"/alidist/aliroot.sh": True}
89
+ with patch.object(os.path, "exists", fake_exists):
90
+ # What to call (longer names deprecated in Python 3.5+)
91
+ if not hasattr(self, "assertRegex"):
92
+ self.assertRegex = self.assertRegexpMatches
93
+ self.assertNotRegex = self.assertNotRegexpMatches
94
+
95
+ # Test: all should go OK (exit with 0)
96
+ out = resetOut()
97
+ with self.assertRaises(SystemExit) as cm:
98
+ args.packages=["Package1"]
99
+ doDoctor(args, MagicMock())
100
+ self.assertEqual(cm.exception.code, 0)
101
+
102
+ # Test: system dependency not found
103
+ out = resetOut()
104
+ with self.assertRaises(SystemExit) as cm:
105
+ args.packages=["SysDep"]
106
+ doDoctor(args, MagicMock())
107
+ self.assertEqual(cm.exception.code, 1)
108
+
109
+ # Test: invalid default
110
+ out = resetOut()
111
+ with self.assertRaises(SystemExit) as cm:
112
+ args.packages=["BreakDefaults"]
113
+ doDoctor(args, MagicMock())
114
+ self.assertEqual(cm.exception.code, 2)
115
+ self.assertRegex(out["error"].getvalue(), "- its_not_there")
116
+
117
+ # Test: common defaults
118
+ out = resetOut()
119
+ with self.assertRaises(SystemExit) as cm:
120
+ args.packages=["TestDef1"]
121
+ doDoctor(args, MagicMock())
122
+ self.assertEqual(cm.exception.code, 2)
123
+ self.assertRegex(out["banner"].getvalue(), "- common_default")
124
+ self.assertNotRegex(out["banner"].getvalue(), "- default1")
125
+ self.assertNotRegex(out["banner"].getvalue(), "- default2")
126
+
127
+ if __name__ == '__main__':
128
+ unittest.main()
tests/test_git.py ADDED
@@ -0,0 +1,48 @@
1
+ import os
2
+ import unittest
3
+
4
+ from alibuild_helpers.git import git
5
+ from alibuild_helpers.scm import SCMError
6
+
7
+ EXISTING_REPO = "https://github.com/alisw/alibuild"
8
+ MISSING_REPO = "https://github.com/alisw/nonexistent"
9
+ PRIVATE_REPO = "https://gitlab.cern.ch/ALICEPrivateExternals/FLUKA.git"
10
+
11
+
12
+ err, out = git(("--help",), check=False)
13
+ @unittest.skipUnless(not err and out.startswith("usage:"),
14
+ "need a working git executable on the system")
15
+ class GitWrapperTestCase(unittest.TestCase):
16
+ """Make sure the git() wrapper function is working."""
17
+
18
+ def setUp(self) -> None:
19
+ # Disable reading all git configuration files, including the user's, in
20
+ # case they have access to PRIVATE_REPO.
21
+ self._prev_git_config_global = os.environ.get("GIT_CONFIG_GLOBAL")
22
+ os.environ["GIT_CONFIG_GLOBAL"] = os.devnull
23
+
24
+ def tearDown(self) -> None:
25
+ # Restore the original value of GIT_CONFIG_GLOBAL, if any.
26
+ if self._prev_git_config_global is None:
27
+ del os.environ["GIT_CONFIG_GLOBAL"]
28
+ else:
29
+ os.environ["GIT_CONFIG_GLOBAL"] = self._prev_git_config_global
30
+
31
+ def test_git_existing_repo(self) -> None:
32
+ """Check git can read an existing repo."""
33
+ err, out = git(("ls-remote", "-ht", EXISTING_REPO),
34
+ check=False, prompt=False)
35
+ self.assertEqual(err, 0, "git output:\n" + out)
36
+ self.assertTrue(out, "expected non-empty output from git")
37
+
38
+ def test_git_missing_repo(self) -> None:
39
+ """Check we get the right exception when a repo doesn't exist."""
40
+ self.assertRaises(SCMError, git, (
41
+ "ls-remote", "-ht", MISSING_REPO,
42
+ ), prompt=False)
43
+
44
+ def test_git_private_repo(self) -> None:
45
+ """Check we get the right exception when credentials are required."""
46
+ self.assertRaises(SCMError, git, (
47
+ "-c", "credential.helper=", "ls-remote", "-ht", PRIVATE_REPO,
48
+ ), prompt=False)
tests/test_hashing.py ADDED
@@ -0,0 +1,67 @@
1
+ import codecs
2
+ import os.path
3
+ import re
4
+ import unittest
5
+
6
+ from collections import OrderedDict
7
+
8
+ from alibuild_helpers.build import storeHashes
9
+
10
+ LOGFILE = "build.log"
11
+ SPEC_RE = re.compile(r"spec = (OrderedDict\(\[\('package', '([^']+)'.*\)\]\))")
12
+ HASH_RE = re.compile(r"Hashes for recipe (.*) are "
13
+ r"(([0-9a-f]{40})(?:, [0-9a-f]{40})*) \(remote\)[,;] "
14
+ r"(([0-9a-f]{40})(?:, [0-9a-f]{40})*) \(local\)")
15
+
16
+
17
+ class KnownGoodHashesTestCase(unittest.TestCase):
18
+ """Make sure storeHashes produces the same hashes as in a build log.
19
+
20
+ It is assumed that the hashes in the build log are correct, i.e. the ones
21
+ we want to get for the matching spec in the log.
22
+
23
+ It is possible to provide old-style logs (mentioning one local and remote
24
+ hash only) or new-style logs (mentioning all alternative remote and local
25
+ hashes). If providing old-style logs, only the hashing for the primary
26
+ hashes is checked.
27
+ """
28
+
29
+ @unittest.skipIf(not os.path.exists(LOGFILE),
30
+ "Need a reference build log at path " + LOGFILE)
31
+ def test_hashes_match_build_log(self) -> None:
32
+ checked = set()
33
+ specs = {}
34
+ with codecs.open(LOGFILE, encoding="utf-8") as logf:
35
+ for line in logf:
36
+ match = re.search(SPEC_RE, line)
37
+ if match:
38
+ spec_expr, package = match.groups()
39
+ specs[package] = eval(spec_expr, {"OrderedDict": OrderedDict})
40
+ specs[package]["is_devel_pkg"] = False
41
+ continue
42
+ match = re.search(HASH_RE, line)
43
+ if not match:
44
+ continue
45
+ package, alt_remote, remote, alt_local, local = match.groups()
46
+ if package in checked:
47
+ # Once a package is built, it will have a second "spec ="
48
+ # and "Hashes for recipe" line in the log. In that case, we
49
+ # don't want to check the hashes are correct, as
50
+ # storeHashes doesn't do anything in that case (the spec
51
+ # from the log will already have hashes stored).
52
+ continue
53
+ storeHashes(package, specs, considerRelocation=False)
54
+ spec = specs[package]
55
+ self.assertEqual(spec["remote_revision_hash"], remote)
56
+ self.assertEqual(spec["local_revision_hash"], local)
57
+ # For logs produced by old hash implementations (which didn't
58
+ # consider spec["scm_refs"]), alt_{remote,local} will only
59
+ # contain the primary hash anyway, so this works nicely.
60
+ self.assertEqual(spec["remote_hashes"], alt_remote.split(", "))
61
+ self.assertEqual(spec["local_hashes"], alt_local.split(", "))
62
+ checked.add(package)
63
+ continue
64
+
65
+
66
+ if __name__ == '__main__':
67
+ unittest.main()
tests/test_init.py ADDED
@@ -0,0 +1,103 @@
1
+ from argparse import Namespace
2
+ import os.path as path
3
+ import os.path
4
+ import unittest
5
+ from unittest.mock import call, patch # In Python 3, mock is built-in
6
+ from io import StringIO
7
+ from collections import OrderedDict
8
+
9
+ from alibuild_helpers.init import doInit, parsePackagesDefinition
10
+
11
+
12
+ def dummy_exists(x):
13
+ return {
14
+ '/sw/MIRROR/aliroot': True,
15
+ }.get(x, False)
16
+
17
+
18
+ CLONE_EVERYTHING = [
19
+ call(["clone", "--origin", "upstream", "https://github.com/alisw/alidist",
20
+ "-b", "master", "/alidist"]),
21
+ call(["clone", "--origin", "upstream", "https://github.com/alisw/AliRoot",
22
+ "--reference", "/sw/MIRROR/aliroot", "-b", "v5-08-00", "./AliRoot"]),
23
+ call(("remote", "set-url", "--push", "upstream",
24
+ "https://github.com/alisw/AliRoot"), directory="./AliRoot"),
25
+ ]
26
+
27
+
28
+ class InitTestCase(unittest.TestCase):
29
+ def test_packageDefinition(self) -> None:
30
+ self.assertEqual(parsePackagesDefinition("AliRoot@v5-08-16,AliPhysics@v5-08-16-01"),
31
+ [{'ver': 'v5-08-16', 'name': 'AliRoot'},
32
+ {'ver': 'v5-08-16-01', 'name': 'AliPhysics'}])
33
+ self.assertEqual(parsePackagesDefinition("AliRoot,AliPhysics@v5-08-16-01"),
34
+ [{'ver': '', 'name': 'AliRoot'},
35
+ {'ver': 'v5-08-16-01', 'name': 'AliPhysics'}])
36
+
37
+ @patch("alibuild_helpers.init.info")
38
+ @patch("alibuild_helpers.init.path")
39
+ @patch("alibuild_helpers.init.os")
40
+ def test_doDryRunInit(self, mock_os, mock_path, mock_info) -> None:
41
+ fake_dist = {"repo": "alisw/alidist", "ver": "master"}
42
+ args = Namespace(
43
+ develPrefix = ".",
44
+ configDir = "/alidist",
45
+ pkgname = "zlib,AliRoot@v5-08-00",
46
+ referenceSources = "/sw/MIRROR",
47
+ dist = fake_dist,
48
+ defaults = "release",
49
+ dryRun = True,
50
+ fetchRepos = False,
51
+ architecture = "slc7_x86-64"
52
+ )
53
+ self.assertRaises(SystemExit, doInit, args)
54
+ self.assertEqual(mock_info.mock_calls, [call('This will initialise local checkouts for %s\n--dry-run / -n specified. Doing nothing.', 'zlib,AliRoot')])
55
+
56
+ @patch("alibuild_helpers.init.banner")
57
+ @patch("alibuild_helpers.init.info")
58
+ @patch("alibuild_helpers.init.path")
59
+ @patch("alibuild_helpers.init.os")
60
+ @patch("alibuild_helpers.init.git")
61
+ @patch("alibuild_helpers.init.updateReferenceRepoSpec")
62
+ @patch("alibuild_helpers.utilities.open")
63
+ @patch("alibuild_helpers.init.readDefaults")
64
+ def test_doRealInit(self, mock_read_defaults, mock_open, mock_update_reference, mock_git, mock_os, mock_path, mock_info, mock_banner) -> None:
65
+ fake_dist = {"repo": "alisw/alidist", "ver": "master"}
66
+ mock_open.side_effect = lambda x: {
67
+ "/alidist/defaults-release.sh": StringIO("package: defaults-release\nversion: v1\n---"),
68
+ "/alidist/aliroot.sh": StringIO("package: AliRoot\nversion: master\nsource: https://github.com/alisw/AliRoot\n---")
69
+ }[x]
70
+ mock_git.return_value = ""
71
+ mock_path.exists.side_effect = dummy_exists
72
+ mock_os.mkdir.return_value = None
73
+ mock_path.join.side_effect = path.join
74
+ mock_read_defaults.return_value = (OrderedDict({"package": "defaults-release", "disable": []}), "")
75
+ args = Namespace(
76
+ develPrefix = ".",
77
+ configDir = "/alidist",
78
+ pkgname = "AliRoot@v5-08-00",
79
+ referenceSources = "/sw/MIRROR",
80
+ dist = fake_dist,
81
+ defaults = "release",
82
+ dryRun = False,
83
+ fetchRepos = False,
84
+ architecture = "slc7_x86-64"
85
+ )
86
+ def fake_exists(n):
87
+ return {"/alidist/aliroot.sh": True}
88
+ with patch.object(os.path, "exists", fake_exists):
89
+ doInit(args)
90
+ self.assertEqual(mock_git.mock_calls, CLONE_EVERYTHING)
91
+ mock_path.exists.assert_has_calls([call('.'), call('/sw/MIRROR'), call('/alidist'), call('./AliRoot')])
92
+
93
+ # Force fetch repos
94
+ mock_git.reset_mock()
95
+ mock_path.reset_mock()
96
+ args.fetchRepos = True
97
+ doInit(args)
98
+ self.assertEqual(mock_git.mock_calls, CLONE_EVERYTHING)
99
+ mock_path.exists.assert_has_calls([call('.'), call('/sw/MIRROR'), call('/alidist'), call('./AliRoot')])
100
+
101
+
102
+ if __name__ == '__main__':
103
+ unittest.main()
tests/test_log.py ADDED
@@ -0,0 +1,50 @@
1
+ import unittest
2
+ from unittest.mock import MagicMock, patch
3
+
4
+ from alibuild_helpers.log import dieOnError, ProgressPrint
5
+
6
+
7
+ class LogTestCase(unittest.TestCase):
8
+ @patch("alibuild_helpers.log.error")
9
+ @patch("alibuild_helpers.log.sys")
10
+ def test_dieOnError(self, mock_sys, mock_error):
11
+ """Check dieOnError dies on error."""
12
+ dieOnError(True, "Message")
13
+ mock_sys.exit.assert_called_once_with(1)
14
+ mock_error.assert_called_once_with("%s", "Message")
15
+ mock_sys.reset_mock()
16
+ mock_error.reset_mock()
17
+ dieOnError(False, "Message")
18
+ mock_error.assert_not_called()
19
+ mock_sys.exit.assert_not_called()
20
+
21
+ @patch("sys.stdout.isatty", new=MagicMock(return_value=True))
22
+ @patch("sys.stderr", new=MagicMock(return_value=True))
23
+ def test_ProgressPrint(self) -> None:
24
+ """Make sure ProgressPrint updates correctly."""
25
+ # ProgressPrint only parses messages every 0.5s. Trick it into thinking
26
+ # the last message was <interval> seconds ago.
27
+ interval = 0.6
28
+ p = ProgressPrint("begin")
29
+ p("%s", "First message")
30
+ self.assertEqual(p.percent, -1)
31
+ p("%s", "[100/200] Update too fast")
32
+ self.assertEqual(p.percent, -1) # unchanged
33
+ p.lasttime -= interval
34
+ p("%s", "Has percentage: 80%")
35
+ self.assertEqual(p.percent, 80)
36
+ p.lasttime -= interval
37
+ p("%s", "No percentage")
38
+ self.assertEqual(p.percent, 80) # unchanged
39
+ p.lasttime -= interval
40
+ p("%s", "[100/200] Building CXX object this/is/a/ninja/test")
41
+ self.assertEqual(p.percent, 50)
42
+ p.lasttime -= interval
43
+ p("%s", "[100/0] Wrong-styled Ninja progress")
44
+ self.assertEqual(p.percent, 50) # unchanged
45
+ p.lasttime -= interval
46
+ p("%s", "Last message")
47
+ p.end()
48
+
49
+ if __name__ == '__main__':
50
+ unittest.main()