1#! /usr/bin/env python3
2#
3# BitBake Toaster Implementation
4#
5# Copyright (C) 2016 Intel Corporation
6#
7# SPDX-License-Identifier: GPL-2.0-only
8#
9
10import os
11import sys
12import time
13import unittest
14
15from orm.models import Project, Release, ProjectTarget, Build, ProjectVariable
16from bldcontrol.models import BuildEnvironment
17
18from bldcontrol.management.commands.runbuilds import Command\
19    as RunBuildsCommand
20
21from django.core.management import call_command
22
23import subprocess
24import logging
25
26logger = logging.getLogger("toaster")
27
28# We use unittest.TestCase instead of django.test.TestCase because we don't
29# want to wrap everything in a database transaction as an external process
30# (bitbake needs access to the database)
31
32def load_build_environment():
33    call_command('loaddata', 'settings.xml', app_label="orm")
34    call_command('loaddata', 'poky.xml', app_label="orm")
35
36    current_builddir = os.environ.get("BUILDDIR")
37    if current_builddir:
38        BuildTest.BUILDDIR = current_builddir
39    else:
40        # Setup a builddir based on default layout
41        # bitbake inside openebedded-core
42        oe_init_build_env_path = os.path.join(
43            os.path.dirname(os.path.abspath(__file__)),
44            os.pardir,
45            os.pardir,
46            os.pardir,
47            os.pardir,
48            os.pardir,
49            'oe-init-build-env'
50        )
51        if not os.path.exists(oe_init_build_env_path):
52            raise Exception("We had no BUILDDIR set and couldn't "
53                            "find oe-init-build-env to set this up "
54                            "ourselves please run oe-init-build-env "
55                            "before running these tests")
56
57        oe_init_build_env_path = os.path.realpath(oe_init_build_env_path)
58        cmd = "bash -c 'source oe-init-build-env %s'" % BuildTest.BUILDDIR
59        p = subprocess.Popen(
60            cmd,
61            cwd=os.path.dirname(oe_init_build_env_path),
62            shell=True,
63            stdout=subprocess.PIPE,
64            stderr=subprocess.PIPE)
65
66        output, err = p.communicate()
67        p.wait()
68
69        logger.info("oe-init-build-env %s %s" % (output, err))
70
71        os.environ['BUILDDIR'] = BuildTest.BUILDDIR
72
73    # Setup the path to bitbake we know where to find this
74    bitbake_path = os.path.join(
75        os.path.dirname(os.path.abspath(__file__)),
76        os.pardir,
77        os.pardir,
78        os.pardir,
79        os.pardir,
80        'bin',
81        'bitbake')
82    if not os.path.exists(bitbake_path):
83        raise Exception("Could not find bitbake at the expected path %s"
84                        % bitbake_path)
85
86    os.environ['BBBASEDIR'] = bitbake_path
87
88class BuildTest(unittest.TestCase):
89
90    PROJECT_NAME = "Testbuild"
91    BUILDDIR = "/tmp/build/"
92
93    def build(self, target):
94        # So that the buildinfo helper uses the test database'
95        self.assertEqual(
96            os.environ.get('DJANGO_SETTINGS_MODULE', ''),
97            'toastermain.settings_test',
98            "Please initialise django with the tests settings:  "
99            "DJANGO_SETTINGS_MODULE='toastermain.settings_test'")
100
101        built = self.target_already_built(target)
102        if built:
103            return built
104
105        load_build_environment()
106
107        BuildEnvironment.objects.get_or_create(
108            betype=BuildEnvironment.TYPE_LOCAL,
109            sourcedir=BuildTest.BUILDDIR,
110            builddir=BuildTest.BUILDDIR
111        )
112
113        release = Release.objects.get(name='local')
114
115        # Create a project for this build to run in
116        project = Project.objects.create_project(name=BuildTest.PROJECT_NAME,
117                                                 release=release)
118
119        if os.environ.get("TOASTER_TEST_USE_SSTATE_MIRROR"):
120            ProjectVariable.objects.get_or_create(
121                name="SSTATE_MIRRORS",
122                value="file://.* http://sstate.yoctoproject.org/PATH;downloadfilename=PATH",
123                project=project)
124
125        ProjectTarget.objects.create(project=project,
126                                     target=target,
127                                     task="")
128        build_request = project.schedule_build()
129
130        # run runbuilds command to dispatch the build
131        # e.g. manage.py runubilds
132        RunBuildsCommand().runbuild()
133
134        build_pk = build_request.build.pk
135        while Build.objects.get(pk=build_pk).outcome == Build.IN_PROGRESS:
136            sys.stdout.write("\rBuilding %s %d%%" %
137                             (target,
138                              build_request.build.completeper()))
139            sys.stdout.flush()
140            time.sleep(1)
141
142        self.assertEqual(Build.objects.get(pk=build_pk).outcome,
143                         Build.SUCCEEDED,
144                         "Build did not SUCCEEDED")
145
146        logger.info("\nBuild finished %s" % build_request.build.outcome)
147        return build_request.build
148
149    def target_already_built(self, target):
150        """ If the target is already built no need to build it again"""
151        for build in Build.objects.filter(
152                project__name=BuildTest.PROJECT_NAME):
153            targets = build.target_set.values_list('target', flat=True)
154            if target in targets:
155                return build
156
157        return None
158