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