1# 2# BitBake Tests for runqueue task processing 3# 4# Copyright (C) 2019 Richard Purdie 5# 6# SPDX-License-Identifier: GPL-2.0-only 7# 8 9import unittest 10import os 11import tempfile 12import subprocess 13import sys 14import time 15 16# 17# TODO: 18# Add tests on task ordering (X happens before Y after Z) 19# 20 21class RunQueueTests(unittest.TestCase): 22 23 alltasks = ['package', 'fetch', 'unpack', 'patch', 'prepare_recipe_sysroot', 'configure', 24 'compile', 'install', 'packagedata', 'package_qa', 'package_write_rpm', 'package_write_ipk', 25 'populate_sysroot', 'build'] 26 a1_sstatevalid = "a1:do_package a1:do_package_qa a1:do_packagedata a1:do_package_write_ipk a1:do_package_write_rpm a1:do_populate_lic a1:do_populate_sysroot" 27 b1_sstatevalid = "b1:do_package b1:do_package_qa b1:do_packagedata b1:do_package_write_ipk b1:do_package_write_rpm b1:do_populate_lic b1:do_populate_sysroot" 28 29 def run_bitbakecmd(self, cmd, builddir, sstatevalid="", slowtasks="", extraenv=None, cleanup=False): 30 env = os.environ.copy() 31 env["BBPATH"] = os.path.realpath(os.path.join(os.path.dirname(__file__), "runqueue-tests")) 32 env["BB_ENV_EXTRAWHITE"] = "SSTATEVALID SLOWTASKS TOPDIR" 33 env["SSTATEVALID"] = sstatevalid 34 env["SLOWTASKS"] = slowtasks 35 env["TOPDIR"] = builddir 36 if extraenv: 37 for k in extraenv: 38 env[k] = extraenv[k] 39 env["BB_ENV_EXTRAWHITE"] = env["BB_ENV_EXTRAWHITE"] + " " + k 40 try: 41 output = subprocess.check_output(cmd, env=env, stderr=subprocess.STDOUT,universal_newlines=True, cwd=builddir) 42 print(output) 43 except subprocess.CalledProcessError as e: 44 self.fail("Command %s failed with %s" % (cmd, e.output)) 45 tasks = [] 46 tasklog = builddir + "/task.log" 47 if os.path.exists(tasklog): 48 with open(tasklog, "r") as f: 49 tasks = [line.rstrip() for line in f] 50 if cleanup: 51 os.remove(tasklog) 52 return tasks 53 54 def test_no_setscenevalid(self): 55 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 56 cmd = ["bitbake", "a1"] 57 sstatevalid = "" 58 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid) 59 expected = ['a1:' + x for x in self.alltasks] 60 self.assertEqual(set(tasks), set(expected)) 61 62 self.shutdown(tempdir) 63 64 def test_single_setscenevalid(self): 65 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 66 cmd = ["bitbake", "a1"] 67 sstatevalid = "a1:do_package" 68 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid) 69 expected = ['a1:package_setscene', 'a1:fetch', 'a1:unpack', 'a1:patch', 'a1:prepare_recipe_sysroot', 'a1:configure', 70 'a1:compile', 'a1:install', 'a1:packagedata', 'a1:package_qa', 'a1:package_write_rpm', 'a1:package_write_ipk', 71 'a1:populate_sysroot', 'a1:build'] 72 self.assertEqual(set(tasks), set(expected)) 73 74 self.shutdown(tempdir) 75 76 def test_intermediate_setscenevalid(self): 77 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 78 cmd = ["bitbake", "a1"] 79 sstatevalid = "a1:do_package a1:do_populate_sysroot" 80 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid) 81 expected = ['a1:package_setscene', 'a1:packagedata', 'a1:package_qa', 'a1:package_write_rpm', 'a1:package_write_ipk', 82 'a1:populate_sysroot_setscene', 'a1:build'] 83 self.assertEqual(set(tasks), set(expected)) 84 85 self.shutdown(tempdir) 86 87 def test_intermediate_notcovered(self): 88 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 89 cmd = ["bitbake", "a1"] 90 sstatevalid = "a1:do_package_qa a1:do_packagedata a1:do_package_write_ipk a1:do_package_write_rpm a1:do_populate_lic a1:do_populate_sysroot" 91 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid) 92 expected = ['a1:package_write_ipk_setscene', 'a1:package_write_rpm_setscene', 'a1:packagedata_setscene', 93 'a1:package_qa_setscene', 'a1:build', 'a1:populate_sysroot_setscene'] 94 self.assertEqual(set(tasks), set(expected)) 95 96 self.shutdown(tempdir) 97 98 def test_all_setscenevalid(self): 99 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 100 cmd = ["bitbake", "a1"] 101 sstatevalid = self.a1_sstatevalid 102 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid) 103 expected = ['a1:package_write_ipk_setscene', 'a1:package_write_rpm_setscene', 'a1:packagedata_setscene', 104 'a1:package_qa_setscene', 'a1:build', 'a1:populate_sysroot_setscene'] 105 self.assertEqual(set(tasks), set(expected)) 106 107 self.shutdown(tempdir) 108 109 def test_no_settasks(self): 110 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 111 cmd = ["bitbake", "a1", "-c", "patch"] 112 sstatevalid = self.a1_sstatevalid 113 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid) 114 expected = ['a1:fetch', 'a1:unpack', 'a1:patch'] 115 self.assertEqual(set(tasks), set(expected)) 116 117 self.shutdown(tempdir) 118 119 def test_mix_covered_notcovered(self): 120 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 121 cmd = ["bitbake", "a1:do_patch", "a1:do_populate_sysroot"] 122 sstatevalid = self.a1_sstatevalid 123 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid) 124 expected = ['a1:fetch', 'a1:unpack', 'a1:patch', 'a1:populate_sysroot_setscene'] 125 self.assertEqual(set(tasks), set(expected)) 126 127 self.shutdown(tempdir) 128 129 # Test targets with intermediate setscene tasks alongside a target with no intermediate setscene tasks 130 def test_mixed_direct_tasks_setscene_tasks(self): 131 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 132 cmd = ["bitbake", "c1:do_patch", "a1"] 133 sstatevalid = self.a1_sstatevalid 134 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid) 135 expected = ['c1:fetch', 'c1:unpack', 'c1:patch', 'a1:package_write_ipk_setscene', 'a1:package_write_rpm_setscene', 'a1:packagedata_setscene', 136 'a1:package_qa_setscene', 'a1:build', 'a1:populate_sysroot_setscene'] 137 self.assertEqual(set(tasks), set(expected)) 138 139 self.shutdown(tempdir) 140 141 # This test slows down the execution of do_package_setscene until after other real tasks have 142 # started running which tests for a bug where tasks were being lost from the buildable list of real 143 # tasks if they weren't in tasks_covered or tasks_notcovered 144 def test_slow_setscene(self): 145 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 146 cmd = ["bitbake", "a1"] 147 sstatevalid = "a1:do_package" 148 slowtasks = "a1:package_setscene" 149 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid, slowtasks) 150 expected = ['a1:package_setscene', 'a1:fetch', 'a1:unpack', 'a1:patch', 'a1:prepare_recipe_sysroot', 'a1:configure', 151 'a1:compile', 'a1:install', 'a1:packagedata', 'a1:package_qa', 'a1:package_write_rpm', 'a1:package_write_ipk', 152 'a1:populate_sysroot', 'a1:build'] 153 self.assertEqual(set(tasks), set(expected)) 154 155 self.shutdown(tempdir) 156 157 def test_setscenewhitelist(self): 158 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 159 cmd = ["bitbake", "a1"] 160 extraenv = { 161 "BB_SETSCENE_ENFORCE" : "1", 162 "BB_SETSCENE_ENFORCE_WHITELIST" : "a1:do_package_write_rpm a1:do_build" 163 } 164 sstatevalid = "a1:do_package a1:do_package_qa a1:do_packagedata a1:do_package_write_ipk a1:do_populate_lic a1:do_populate_sysroot" 165 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid, extraenv=extraenv) 166 expected = ['a1:packagedata_setscene', 'a1:package_qa_setscene', 'a1:package_write_ipk_setscene', 167 'a1:populate_sysroot_setscene', 'a1:package_setscene'] 168 self.assertEqual(set(tasks), set(expected)) 169 170 self.shutdown(tempdir) 171 172 # Tests for problems with dependencies between setscene tasks 173 def test_no_setscenevalid_harddeps(self): 174 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 175 cmd = ["bitbake", "d1"] 176 sstatevalid = "" 177 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid) 178 expected = ['a1:package', 'a1:fetch', 'a1:unpack', 'a1:patch', 'a1:prepare_recipe_sysroot', 'a1:configure', 179 'a1:compile', 'a1:install', 'a1:packagedata', 'a1:package_write_rpm', 'a1:package_write_ipk', 180 'a1:populate_sysroot', 'd1:package', 'd1:fetch', 'd1:unpack', 'd1:patch', 'd1:prepare_recipe_sysroot', 'd1:configure', 181 'd1:compile', 'd1:install', 'd1:packagedata', 'd1:package_qa', 'd1:package_write_rpm', 'd1:package_write_ipk', 182 'd1:populate_sysroot', 'd1:build'] 183 self.assertEqual(set(tasks), set(expected)) 184 185 self.shutdown(tempdir) 186 187 def test_no_setscenevalid_withdeps(self): 188 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 189 cmd = ["bitbake", "b1"] 190 sstatevalid = "" 191 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid) 192 expected = ['a1:' + x for x in self.alltasks] + ['b1:' + x for x in self.alltasks] 193 expected.remove('a1:build') 194 expected.remove('a1:package_qa') 195 self.assertEqual(set(tasks), set(expected)) 196 197 self.shutdown(tempdir) 198 199 def test_single_a1_setscenevalid_withdeps(self): 200 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 201 cmd = ["bitbake", "b1"] 202 sstatevalid = "a1:do_package" 203 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid) 204 expected = ['a1:package_setscene', 'a1:fetch', 'a1:unpack', 'a1:patch', 'a1:prepare_recipe_sysroot', 'a1:configure', 205 'a1:compile', 'a1:install', 'a1:packagedata', 'a1:package_write_rpm', 'a1:package_write_ipk', 206 'a1:populate_sysroot'] + ['b1:' + x for x in self.alltasks] 207 self.assertEqual(set(tasks), set(expected)) 208 209 self.shutdown(tempdir) 210 211 def test_single_b1_setscenevalid_withdeps(self): 212 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 213 cmd = ["bitbake", "b1"] 214 sstatevalid = "b1:do_package" 215 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid) 216 expected = ['a1:package', 'a1:fetch', 'a1:unpack', 'a1:patch', 'a1:prepare_recipe_sysroot', 'a1:configure', 217 'a1:compile', 'a1:install', 'a1:packagedata', 'a1:package_write_rpm', 'a1:package_write_ipk', 218 'a1:populate_sysroot', 'b1:package_setscene'] + ['b1:' + x for x in self.alltasks] 219 expected.remove('b1:package') 220 self.assertEqual(set(tasks), set(expected)) 221 222 self.shutdown(tempdir) 223 224 def test_intermediate_setscenevalid_withdeps(self): 225 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 226 cmd = ["bitbake", "b1"] 227 sstatevalid = "a1:do_package a1:do_populate_sysroot b1:do_package" 228 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid) 229 expected = ['a1:package_setscene', 'a1:packagedata', 'a1:package_write_rpm', 'a1:package_write_ipk', 230 'a1:populate_sysroot_setscene', 'b1:package_setscene'] + ['b1:' + x for x in self.alltasks] 231 expected.remove('b1:package') 232 self.assertEqual(set(tasks), set(expected)) 233 234 self.shutdown(tempdir) 235 236 def test_all_setscenevalid_withdeps(self): 237 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 238 cmd = ["bitbake", "b1"] 239 sstatevalid = self.a1_sstatevalid + " " + self.b1_sstatevalid 240 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid) 241 expected = ['a1:package_write_ipk_setscene', 'a1:package_write_rpm_setscene', 'a1:packagedata_setscene', 242 'b1:build', 'a1:populate_sysroot_setscene', 'b1:package_write_ipk_setscene', 'b1:package_write_rpm_setscene', 243 'b1:packagedata_setscene', 'b1:package_qa_setscene', 'b1:populate_sysroot_setscene'] 244 self.assertEqual(set(tasks), set(expected)) 245 246 self.shutdown(tempdir) 247 248 def test_multiconfig_setscene_optimise(self): 249 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 250 extraenv = { 251 "BBMULTICONFIG" : "mc-1 mc_2", 252 "BB_SIGNATURE_HANDLER" : "basic" 253 } 254 cmd = ["bitbake", "b1", "mc:mc-1:b1", "mc:mc_2:b1"] 255 setscenetasks = ['package_write_ipk_setscene', 'package_write_rpm_setscene', 'packagedata_setscene', 256 'populate_sysroot_setscene', 'package_qa_setscene'] 257 sstatevalid = "" 258 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid, extraenv=extraenv) 259 expected = ['a1:' + x for x in self.alltasks] + ['b1:' + x for x in self.alltasks] + \ 260 ['mc-1:b1:' + x for x in setscenetasks] + ['mc-1:a1:' + x for x in setscenetasks] + \ 261 ['mc_2:b1:' + x for x in setscenetasks] + ['mc_2:a1:' + x for x in setscenetasks] + \ 262 ['mc-1:b1:build', 'mc_2:b1:build'] 263 for x in ['mc-1:a1:package_qa_setscene', 'mc_2:a1:package_qa_setscene', 'a1:build', 'a1:package_qa']: 264 expected.remove(x) 265 self.assertEqual(set(tasks), set(expected)) 266 267 self.shutdown(tempdir) 268 269 def test_multiconfig_bbmask(self): 270 # This test validates that multiconfigs can independently mask off 271 # recipes they do not want with BBMASK. It works by having recipes 272 # that will fail to parse for mc-1 and mc_2, then making each multiconfig 273 # build the one that does parse. This ensures that the recipes are in 274 # each multiconfigs BBFILES, but each is masking only the one that 275 # doesn't parse 276 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 277 extraenv = { 278 "BBMULTICONFIG" : "mc-1 mc_2", 279 "BB_SIGNATURE_HANDLER" : "basic", 280 "EXTRA_BBFILES": "${COREBASE}/recipes/fails-mc/*.bb", 281 } 282 cmd = ["bitbake", "mc:mc-1:fails-mc2", "mc:mc_2:fails-mc1"] 283 self.run_bitbakecmd(cmd, tempdir, "", extraenv=extraenv) 284 285 self.shutdown(tempdir) 286 287 def test_multiconfig_mcdepends(self): 288 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 289 extraenv = { 290 "BBMULTICONFIG" : "mc-1 mc_2", 291 "BB_SIGNATURE_HANDLER" : "TestMulticonfigDepends", 292 "EXTRA_BBFILES": "${COREBASE}/recipes/fails-mc/*.bb", 293 } 294 tasks = self.run_bitbakecmd(["bitbake", "mc:mc-1:f1"], tempdir, "", extraenv=extraenv, cleanup=True) 295 expected = ["mc-1:f1:%s" % t for t in self.alltasks] + \ 296 ["mc_2:a1:%s" % t for t in self.alltasks] 297 self.assertEqual(set(tasks), set(expected)) 298 299 # A rebuild does nothing 300 tasks = self.run_bitbakecmd(["bitbake", "mc:mc-1:f1"], tempdir, "", extraenv=extraenv, cleanup=True) 301 self.assertEqual(set(tasks), set()) 302 303 # Test that a signature change in the dependent task causes 304 # mcdepends to rebuild 305 tasks = self.run_bitbakecmd(["bitbake", "mc:mc_2:a1", "-c", "compile", "-f"], tempdir, "", extraenv=extraenv, cleanup=True) 306 expected = ["mc_2:a1:compile"] 307 self.assertEqual(set(tasks), set(expected)) 308 309 rerun_tasks = self.alltasks[:] 310 for x in ("fetch", "unpack", "patch", "prepare_recipe_sysroot", "configure", "compile"): 311 rerun_tasks.remove(x) 312 tasks = self.run_bitbakecmd(["bitbake", "mc:mc-1:f1"], tempdir, "", extraenv=extraenv, cleanup=True) 313 expected = ["mc-1:f1:%s" % t for t in rerun_tasks] + \ 314 ["mc_2:a1:%s" % t for t in rerun_tasks] 315 self.assertEqual(set(tasks), set(expected)) 316 317 self.shutdown(tempdir) 318 319 def test_hashserv_single(self): 320 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 321 extraenv = { 322 "BB_HASHSERVE" : "auto", 323 "BB_SIGNATURE_HANDLER" : "TestEquivHash" 324 } 325 cmd = ["bitbake", "a1", "b1"] 326 setscenetasks = ['package_write_ipk_setscene', 'package_write_rpm_setscene', 'packagedata_setscene', 327 'populate_sysroot_setscene', 'package_qa_setscene'] 328 sstatevalid = "" 329 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid, extraenv=extraenv, cleanup=True) 330 expected = ['a1:' + x for x in self.alltasks] + ['b1:' + x for x in self.alltasks] 331 self.assertEqual(set(tasks), set(expected)) 332 cmd = ["bitbake", "a1", "-c", "install", "-f"] 333 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid, extraenv=extraenv, cleanup=True) 334 expected = ['a1:install'] 335 self.assertEqual(set(tasks), set(expected)) 336 cmd = ["bitbake", "a1", "b1"] 337 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid, extraenv=extraenv, cleanup=True) 338 expected = ['a1:populate_sysroot', 'a1:package', 'a1:package_write_rpm_setscene', 'a1:packagedata_setscene', 339 'a1:package_write_ipk_setscene', 'a1:package_qa_setscene', 'a1:build'] 340 self.assertEqual(set(tasks), set(expected)) 341 342 self.shutdown(tempdir) 343 344 def test_hashserv_double(self): 345 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 346 extraenv = { 347 "BB_HASHSERVE" : "auto", 348 "BB_SIGNATURE_HANDLER" : "TestEquivHash" 349 } 350 cmd = ["bitbake", "a1", "b1", "e1"] 351 setscenetasks = ['package_write_ipk_setscene', 'package_write_rpm_setscene', 'packagedata_setscene', 352 'populate_sysroot_setscene', 'package_qa_setscene'] 353 sstatevalid = "" 354 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid, extraenv=extraenv, cleanup=True) 355 expected = ['a1:' + x for x in self.alltasks] + ['b1:' + x for x in self.alltasks] + ['e1:' + x for x in self.alltasks] 356 self.assertEqual(set(tasks), set(expected)) 357 cmd = ["bitbake", "a1", "b1", "-c", "install", "-fn"] 358 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid, extraenv=extraenv, cleanup=True) 359 cmd = ["bitbake", "e1"] 360 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid, extraenv=extraenv, cleanup=True) 361 expected = ['a1:package', 'a1:install', 'b1:package', 'b1:install', 'a1:populate_sysroot', 'b1:populate_sysroot', 362 'a1:package_write_ipk_setscene', 'b1:packagedata_setscene', 'b1:package_write_rpm_setscene', 363 'a1:package_write_rpm_setscene', 'b1:package_write_ipk_setscene', 'a1:packagedata_setscene'] 364 self.assertEqual(set(tasks), set(expected)) 365 366 self.shutdown(tempdir) 367 368 def test_hashserv_multiple_setscene(self): 369 # Runs e1:do_package_setscene twice 370 with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir: 371 extraenv = { 372 "BB_HASHSERVE" : "auto", 373 "BB_SIGNATURE_HANDLER" : "TestEquivHash" 374 } 375 cmd = ["bitbake", "a1", "b1", "e1"] 376 setscenetasks = ['package_write_ipk_setscene', 'package_write_rpm_setscene', 'packagedata_setscene', 377 'populate_sysroot_setscene', 'package_qa_setscene'] 378 sstatevalid = "" 379 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid, extraenv=extraenv, cleanup=True) 380 expected = ['a1:' + x for x in self.alltasks] + ['b1:' + x for x in self.alltasks] + ['e1:' + x for x in self.alltasks] 381 self.assertEqual(set(tasks), set(expected)) 382 cmd = ["bitbake", "a1", "b1", "-c", "install", "-fn"] 383 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid, extraenv=extraenv, cleanup=True) 384 cmd = ["bitbake", "e1"] 385 sstatevalid = "e1:do_package" 386 tasks = self.run_bitbakecmd(cmd, tempdir, sstatevalid, extraenv=extraenv, cleanup=True, slowtasks="a1:populate_sysroot b1:populate_sysroot") 387 expected = ['a1:package', 'a1:install', 'b1:package', 'b1:install', 'a1:populate_sysroot', 'b1:populate_sysroot', 388 'a1:package_write_ipk_setscene', 'b1:packagedata_setscene', 'b1:package_write_rpm_setscene', 389 'a1:package_write_rpm_setscene', 'b1:package_write_ipk_setscene', 'a1:packagedata_setscene', 390 'e1:package_setscene'] 391 self.assertEqual(set(tasks), set(expected)) 392 for i in expected: 393 self.assertEqual(tasks.count(i), 1, "%s not in task list once" % i) 394 395 self.shutdown(tempdir) 396 397 def shutdown(self, tempdir): 398 # Wait for the hashserve socket to disappear else we'll see races with the tempdir cleanup 399 while (os.path.exists(tempdir + "/hashserve.sock") or os.path.exists(tempdir + "cache/hashserv.db-wal") or os.path.exists(tempdir + "/bitbake.lock")): 400 time.sleep(0.5) 401 402