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_PASSTHROUGH_ADDITIONS"] = "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_PASSTHROUGH_ADDITIONS"] = env["BB_ENV_PASSTHROUGH_ADDITIONS"] + " " + 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_setscene_ignore_tasks(self):
158        with tempfile.TemporaryDirectory(prefix="runqueuetest") as tempdir:
159            cmd = ["bitbake", "a1"]
160            extraenv = {
161                "BB_SETSCENE_ENFORCE" : "1",
162                "BB_SETSCENE_ENFORCE_IGNORE_TASKS" : "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" : "basichash",
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