summaryrefslogtreecommitdiff
path: root/import-layers/yocto-poky/bitbake/lib/toaster
diff options
context:
space:
mode:
Diffstat (limited to 'import-layers/yocto-poky/bitbake/lib/toaster')
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py164
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py21
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/tests.py141
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/contrib/README6
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/README41
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/TODO9
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/config.py98
-rwxr-xr-ximport-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/launcher.py101
-rwxr-xr-ximport-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/recv.py56
-rwxr-xr-ximport-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/runner.py222
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/settings.json5
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/shellutils.py141
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/tests.py115
-rwxr-xr-ximport-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/run_toastertests.py155
-rwxr-xr-ximport-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py2376
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/toaster_test.cfg25
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/urlcheck.py53
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/urllist.py39
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/oe-core.xml38
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/poky.xml103
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/settings.xml2
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/orm/management/commands/lsupdates.py63
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/orm/models.py126
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/README25
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py13
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py3
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/buildtest.py135
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/test_core_image_min.py50
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/tests/commands/__init__.py (renamed from import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/log/.create)0
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/tests/commands/test_loaddata.py61
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/tests/commands/test_lsupdates.py45
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/tests/commands/test_runbuilds.py88
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/tests/eventreplay/README22
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/tests/eventreplay/__init__.py97
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/tests/functional/__init__.py0
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/tests/functional/functional_helpers.py122
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/tests/functional/test_functional_basic.py243
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/tests/views/README4
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/tests/views/__init__.py0
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/tests/views/test_views.py (renamed from import-layers/yocto-poky/bitbake/lib/toaster/toastergui/tests.py)130
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/api.py166
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/buildtables.py3
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/default.css1
-rwxr-xr-ximport-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/prettify.css1
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/customrecipe.js6
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/highlight.pack.js2
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/importlayer.js55
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/libtoaster.js12
-rwxr-xr-ximport-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/prettify.js28
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/projectpage.js17
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/table.js21
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/tests/test.js8
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/base.html10
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basebuildpage.html20
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/importlayer.html11
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/js-unit-tests.html2
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/layer_btn.html15
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/layerdetails.html1
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/machine_btn.html7
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/project.html3
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projectconf.html11
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipe_btn.html8
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/typeaheads.py112
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/urls.py11
-rwxr-xr-ximport-layers/yocto-poky/bitbake/lib/toaster/toastergui/views.py148
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastergui/widgets.py8
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastermain/settings.py3
-rw-r--r--import-layers/yocto-poky/bitbake/lib/toaster/toastermain/settings_production_example.py58
68 files changed, 1677 insertions, 4209 deletions
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py
index e5f7c988c..12071029a 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py
@@ -27,7 +27,7 @@ import shutil
from django.db import transaction
from django.db.models import Q
from bldcontrol.models import BuildEnvironment, BRLayer, BRVariable, BRTarget, BRBitbake
-from orm.models import CustomImageRecipe, Layer, Layer_Version, ProjectLayer
+from orm.models import CustomImageRecipe, Layer, Layer_Version, ProjectLayer, ToasterSetting
import subprocess
from toastermain import settings
@@ -200,72 +200,93 @@ class LocalhostBEController(BuildEnvironmentController):
logger.debug("localhostbecontroller: current layer list %s " % pformat(layerlist))
+ if self.pokydirname is None and os.path.exists(os.path.join(self.be.sourcedir, "oe-init-build-env")):
+ logger.debug("localhostbecontroller: selected poky dir name %s" % self.be.sourcedir)
+ self.pokydirname = self.be.sourcedir
+
# 5. create custom layer and add custom recipes to it
- layerpath = os.path.join(self.be.builddir,
- CustomImageRecipe.LAYER_NAME)
for target in targets:
try:
- customrecipe = CustomImageRecipe.objects.get(name=target.target,
- project=bitbake.req.project)
+ customrecipe = CustomImageRecipe.objects.get(
+ name=target.target,
+ project=bitbake.req.project)
+
+ custom_layer_path = self.setup_custom_image_recipe(
+ customrecipe, layers)
+
+ if os.path.isdir(custom_layer_path):
+ layerlist.append(custom_layer_path)
+
except CustomImageRecipe.DoesNotExist:
- continue # not a custom recipe, skip
-
- # create directory structure
- for name in ("conf", "recipes"):
- path = os.path.join(layerpath, name)
- if not os.path.isdir(path):
- os.makedirs(path)
-
- # create layer.oonf
- config = os.path.join(layerpath, "conf", "layer.conf")
- if not os.path.isfile(config):
- with open(config, "w") as conf:
- conf.write('BBPATH .= ":${LAYERDIR}"\nBBFILES += "${LAYERDIR}/recipes/*.bb"\n')
-
- # Update the Layer_Version dirpath that has our base_recipe in
- # to be able to read the base recipe to then generate the
- # custom recipe.
- br_layer_base_recipe = layers.get(
- layer_version=customrecipe.base_recipe.layer_version)
-
- br_layer_base_dirpath = \
- os.path.join(self.be.sourcedir,
- self.getGitCloneDirectory(
- br_layer_base_recipe.giturl,
- br_layer_base_recipe.commit),
- customrecipe.base_recipe.layer_version.dirpath
- )
-
- customrecipe.base_recipe.layer_version.dirpath = \
- br_layer_base_dirpath
-
- customrecipe.base_recipe.layer_version.save()
-
- # create recipe
- recipe_path = \
- os.path.join(layerpath, "recipes", "%s.bb" % target.target)
- with open(recipe_path, "w") as recipef:
- recipef.write(customrecipe.generate_recipe_file_contents())
-
- # Update the layer and recipe objects
- customrecipe.layer_version.dirpath = layerpath
- customrecipe.layer_version.save()
-
- customrecipe.file_path = recipe_path
- customrecipe.save()
-
- # create *Layer* objects needed for build machinery to work
- BRLayer.objects.get_or_create(req=target.req,
- name=layer.name,
- dirpath=layerpath,
- giturl="file://%s" % layerpath)
- if os.path.isdir(layerpath):
- layerlist.append(layerpath)
+ continue # not a custom recipe, skip
- self.islayerset = True
layerlist.extend(nongitlayerlist)
+ logger.debug("\n\nset layers gives this list %s" % pformat(layerlist))
+ self.islayerset = True
return layerlist
+ def setup_custom_image_recipe(self, customrecipe, layers):
+ """ Set up toaster-custom-images layer and recipe files """
+ layerpath = os.path.join(self.be.builddir,
+ CustomImageRecipe.LAYER_NAME)
+
+ # create directory structure
+ for name in ("conf", "recipes"):
+ path = os.path.join(layerpath, name)
+ if not os.path.isdir(path):
+ os.makedirs(path)
+
+ # create layer.conf
+ config = os.path.join(layerpath, "conf", "layer.conf")
+ if not os.path.isfile(config):
+ with open(config, "w") as conf:
+ conf.write('BBPATH .= ":${LAYERDIR}"\nBBFILES += "${LAYERDIR}/recipes/*.bb"\n')
+
+ # Update the Layer_Version dirpath that has our base_recipe in
+ # to be able to read the base recipe to then generate the
+ # custom recipe.
+ br_layer_base_recipe = layers.get(
+ layer_version=customrecipe.base_recipe.layer_version)
+
+ # If the layer is one that we've cloned we know where it lives
+ if br_layer_base_recipe.giturl and br_layer_base_recipe.commit:
+ layer_path = self.getGitCloneDirectory(
+ br_layer_base_recipe.giturl,
+ br_layer_base_recipe.commit)
+ # Otherwise it's a local layer
+ elif br_layer_base_recipe.local_source_dir:
+ layer_path = br_layer_base_recipe.local_source_dir
+ else:
+ logger.error("Unable to workout the dir path for the custom"
+ " image recipe")
+
+ br_layer_base_dirpath = os.path.join(
+ self.be.sourcedir,
+ layer_path,
+ customrecipe.base_recipe.layer_version.dirpath)
+
+ customrecipe.base_recipe.layer_version.dirpath = br_layer_base_dirpath
+
+ customrecipe.base_recipe.layer_version.save()
+
+ # create recipe
+ recipe_path = os.path.join(layerpath, "recipes", "%s.bb" %
+ customrecipe.name)
+ with open(recipe_path, "w") as recipef:
+ recipef.write(customrecipe.generate_recipe_file_contents())
+
+ # Update the layer and recipe objects
+ customrecipe.layer_version.dirpath = layerpath
+ customrecipe.layer_version.layer.local_source_dir = layerpath
+ customrecipe.layer_version.layer.save()
+ customrecipe.layer_version.save()
+
+ customrecipe.file_path = recipe_path
+ customrecipe.save()
+
+ return layerpath
+
+
def readServerLogFile(self):
return open(os.path.join(self.be.builddir, "toaster_server.log"), "r").read()
@@ -277,23 +298,17 @@ class LocalhostBEController(BuildEnvironmentController):
builddir = '%s-toaster-%d' % (self.be.builddir, bitbake.req.project.id)
oe_init = os.path.join(self.pokydirname, 'oe-init-build-env')
# init build environment
- self._shellcmd("bash -c 'source %s %s'" % (oe_init, builddir),
+ try:
+ custom_script = ToasterSetting.objects.get(name="CUSTOM_BUILD_INIT_SCRIPT").value
+ custom_script = custom_script.replace("%BUILDDIR%" ,builddir)
+ self._shellcmd("bash -c 'source %s'" % (custom_script))
+ except ToasterSetting.DoesNotExist:
+ self._shellcmd("bash -c 'source %s %s'" % (oe_init, builddir),
self.be.sourcedir)
# update bblayers.conf
- bblconfpath = os.path.join(builddir, "conf/bblayers.conf")
- conflines = open(bblconfpath, "r").readlines()
- skip = False
+ bblconfpath = os.path.join(builddir, "conf/toaster-bblayers.conf")
with open(bblconfpath, 'w') as bblayers:
- for line in conflines:
- if line.startswith("# line added by toaster"):
- skip = True
- continue
- if skip:
- skip = False
- else:
- bblayers.write(line)
-
bblayers.write('# line added by toaster build control\n'
'BBLAYERS = "%s"' % ' '.join(layers))
@@ -306,9 +321,10 @@ class LocalhostBEController(BuildEnvironmentController):
# run bitbake server from the clone
bitbake = os.path.join(self.pokydirname, 'bitbake', 'bin', 'bitbake')
- self._shellcmd('bash -c \"source %s %s; BITBAKE_UI="knotty" %s --read %s '
+ toasterlayers = os.path.join(builddir,"conf/toaster-bblayers.conf")
+ self._shellcmd('bash -c \"source %s %s; BITBAKE_UI="knotty" %s --read %s --read %s '
'--server-only -t xmlrpc -B 0.0.0.0:0\"' % (oe_init,
- builddir, bitbake, confpath), self.be.sourcedir)
+ builddir, bitbake, confpath, toasterlayers), self.be.sourcedir)
# read port number from bitbake.lock
self.be.bbport = ""
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py
index 7f7a5a955..df11f9d16 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/management/commands/runbuilds.py
@@ -11,9 +11,11 @@ from orm.models import Build, LogMessage, Target
import logging
import traceback
import signal
+import os
logger = logging.getLogger("toaster")
+
class Command(NoArgsCommand):
args = ""
help = "Schedules and executes build requests as possible. "\
@@ -50,7 +52,7 @@ class Command(NoArgsCommand):
logger.debug("runbuilds: No build env")
return
- logger.info("runbuilds: starting build %s, environment %s" % \
+ logger.info("runbuilds: starting build %s, environment %s" %
(br, bec.be))
# let the build request know where it is being executed
@@ -80,7 +82,7 @@ class Command(NoArgsCommand):
def archive(self):
for br in BuildRequest.objects.filter(state=BuildRequest.REQ_ARCHIVE):
- if br.build == None:
+ if br.build is None:
br.state = BuildRequest.REQ_FAILED
else:
br.state = BuildRequest.REQ_COMPLETED
@@ -99,10 +101,10 @@ class Command(NoArgsCommand):
Q(updated__lt=timezone.now() - timedelta(seconds=30))
).update(lock=BuildEnvironment.LOCK_FREE)
-
# update all Builds that were in progress and failed to start
- for br in BuildRequest.objects.filter(state=BuildRequest.REQ_FAILED,
- build__outcome=Build.IN_PROGRESS):
+ for br in BuildRequest.objects.filter(
+ state=BuildRequest.REQ_FAILED,
+ build__outcome=Build.IN_PROGRESS):
# transpose the launch errors in ToasterExceptions
br.build.outcome = Build.FAILED
for brerror in br.brerror_set.all():
@@ -117,7 +119,6 @@ class Command(NoArgsCommand):
br.environment.lock = BuildEnvironment.LOCK_FREE
br.environment.save()
-
# update all BuildRequests without a build created
for br in BuildRequest.objects.filter(build=None):
br.build = Build.objects.create(project=br.project,
@@ -144,7 +145,7 @@ class Command(NoArgsCommand):
# Make sure the LOCK is removed for builds which have been fully
# cancelled
- for br in BuildRequest.objects.filter(\
+ for br in BuildRequest.objects.filter(
Q(build__outcome=Build.CANCELLED) &
Q(state=BuildRequest.REQ_CANCELLING) &
~Q(environment=None)):
@@ -168,6 +169,12 @@ class Command(NoArgsCommand):
logger.warn("runbuilds: schedule exception %s" % str(e))
def handle_noargs(self, **options):
+ pidfile_path = os.path.join(os.environ.get("BUILDDIR", "."),
+ ".runbuilds.pid")
+
+ with open(pidfile_path, 'w') as pidfile:
+ pidfile.write("%s" % os.getpid())
+
self.runbuild()
signal.signal(signal.SIGUSR1, lambda sig, frame: None)
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/tests.py b/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/tests.py
deleted file mode 100644
index 475ac0a16..000000000
--- a/import-layers/yocto-poky/bitbake/lib/toaster/bldcontrol/tests.py
+++ /dev/null
@@ -1,141 +0,0 @@
-"""
-This file demonstrates writing tests using the unittest module. These will pass
-when you run "manage.py test".
-
-Replace this with more appropriate tests for your application.
-"""
-
-from django.test import TestCase
-
-from bldcontrol.bbcontroller import BitbakeController, BuildSetupException
-from bldcontrol.localhostbecontroller import LocalhostBEController
-from bldcontrol.models import BuildEnvironment, BuildRequest
-from bldcontrol.management.commands.runbuilds import Command
-
-import socket
-import subprocess
-import os
-
-# standard poky data hardcoded for testing
-BITBAKE_LAYER = type('bitbake_info', (object,), { "giturl": "git://git.yoctoproject.org/poky.git", "dirpath": "", "commit": "HEAD"})
-POKY_LAYERS = [
- type('poky_info', (object,), { "name": "meta", "giturl": "git://git.yoctoproject.org/poky.git", "dirpath": "meta", "commit": "HEAD"}),
- type('poky_info', (object,), { "name": "meta-yocto", "giturl": "git://git.yoctoproject.org/poky.git", "dirpath": "meta-yocto", "commit": "HEAD"}),
- type('poky_info', (object,), { "name": "meta-yocto-bsp", "giturl": "git://git.yoctoproject.org/poky.git", "dirpath": "meta-yocto-bsp", "commit": "HEAD"}),
- ]
-
-
-
-# we have an abstract test class designed to ensure that the controllers use a single interface
-# specific controller tests only need to override the _getBuildEnvironment() method
-
-test_sourcedir = os.getenv("TTS_SOURCE_DIR")
-test_builddir = os.getenv("TTS_BUILD_DIR")
-test_address = os.getenv("TTS_TEST_ADDRESS", "localhost")
-
-if test_sourcedir == None or test_builddir == None or test_address == None:
- raise Exception("Please set TTTS_SOURCE_DIR, TTS_BUILD_DIR and TTS_TEST_ADDRESS")
-
-# The bb server will expect a toaster-pre.conf file to exist. If it doesn't exit then we make
-# an empty one here.
-open(test_builddir + 'conf/toaster-pre.conf', 'a').close()
-
-class BEControllerTests(object):
-
- def _serverForceStop(self, bc):
- err = bc._shellcmd("netstat -tapn 2>/dev/null | grep 8200 | awk '{print $7}' | sort -fu | cut -d \"/\" -f 1 | grep -v -- - | tee /dev/fd/2 | xargs -r kill")
- self.assertTrue(err == '', "bitbake server pid %s not stopped" % err)
-
- def test_serverStartAndStop(self):
- obe = self._getBuildEnvironment()
- bc = self._getBEController(obe)
- try:
- # setting layers, skip any layer info
- bc.setLayers(BITBAKE_LAYER, POKY_LAYERS)
- except NotImplementedError:
- print("Test skipped due to command not implemented yet")
- return True
- # We are ok with the exception as we're handling the git already exists
- except BuildSetupException:
- pass
-
- bc.pokydirname = test_sourcedir
- bc.islayerset = True
-
- hostname = test_address.split("@")[-1]
-
- # test start server and stop
- bc.startBBServer()
-
- self.assertFalse(socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect_ex((hostname, int(bc.be.bbport))), "Server not answering")
-
- self._serverForceStop(bc)
-
- def test_getBBController(self):
- obe = self._getBuildEnvironment()
- bc = self._getBEController(obe)
- layerSet = False
- try:
- # setting layers, skip any layer info
- layerSet = bc.setLayers(BITBAKE_LAYER, POKY_LAYERS)
- except NotImplementedError:
- print("Test skipped due to command not implemented yet")
- return True
- # We are ok with the exception as we're handling the git already exists
- except BuildSetupException:
- pass
-
- bc.pokydirname = test_sourcedir
- bc.islayerset = True
-
- bbc = bc.getBBController()
- self.assertTrue(isinstance(bbc, BitbakeController))
-
- self._serverForceStop(bc)
-
-class LocalhostBEControllerTests(TestCase, BEControllerTests):
- def __init__(self, *args):
- super(LocalhostBEControllerTests, self).__init__(*args)
-
-
- def _getBuildEnvironment(self):
- return BuildEnvironment.objects.create(
- lock = BuildEnvironment.LOCK_FREE,
- betype = BuildEnvironment.TYPE_LOCAL,
- address = test_address,
- sourcedir = test_sourcedir,
- builddir = test_builddir )
-
- def _getBEController(self, obe):
- return LocalhostBEController(obe)
-
-class RunBuildsCommandTests(TestCase):
- def test_bec_select(self):
- """
- Tests that we can find and lock a build environment
- """
-
- obe = BuildEnvironment.objects.create(lock = BuildEnvironment.LOCK_FREE, betype = BuildEnvironment.TYPE_LOCAL)
- command = Command()
- bec = command._selectBuildEnvironment()
-
- # make sure we select the object we've just built
- self.assertTrue(bec.be.id == obe.id, "Environment is not properly selected")
- # we have a locked environment
- self.assertTrue(bec.be.lock == BuildEnvironment.LOCK_LOCK, "Environment is not locked")
- # no more selections possible here
- self.assertRaises(IndexError, command._selectBuildEnvironment)
-
- def test_br_select(self):
- from orm.models import Project, Release, BitbakeVersion, Branch
- p = Project.objects.create_project("test", Release.objects.get_or_create(name = "HEAD", bitbake_version = BitbakeVersion.objects.get_or_create(name="HEAD", branch=Branch.objects.get_or_create(name="HEAD"))[0])[0])
- obr = BuildRequest.objects.create(state = BuildRequest.REQ_QUEUED, project = p)
- command = Command()
- br = command._selectBuildRequest()
-
- # make sure we select the object we've just built
- self.assertTrue(obr.id == br.id, "Request is not properly selected")
- # we have a locked environment
- self.assertTrue(br.state == BuildRequest.REQ_INPROGRESS, "Request is not updated")
- # no more selections possible here
- self.assertRaises(IndexError, command._selectBuildRequest)
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/README b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/README
deleted file mode 100644
index 46d0ff008..000000000
--- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/README
+++ /dev/null
@@ -1,6 +0,0 @@
-contrib directory for toaster
-
-This directory holds code that works with Toaster, without being an integral part of the Toaster project.
-It is intended for testing code, testing fixtures, tools for Toaster, etc.
-
-NOTE: This directory is NOT a Python module.
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/README b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/README
deleted file mode 100644
index 22fa5673b..000000000
--- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/README
+++ /dev/null
@@ -1,41 +0,0 @@
-
-Toaster Testing Framework
-Yocto Project
-
-
-Rationale
-------------
-As Toaster contributions grow with the number of people that contribute code, verifying each patch prior to submitting upstream becomes a hard-to-scale problem for humans. We devised this system in order to run patch-level validation, trying to eliminate common problems from submitted patches, in an automated fashion.
-
-The Toaster Testing Framework is a set of Python scripts that provides an extensible way to write smoke and regression tests that will be run on each patch set sent for review on the toaster mailing list.
-
-
-Usage
-------------
-There are three main executable scripts in this directory.
- * runner.py is designed to be run from the command line. It requires, as mandatory parameter, a branch name on poky-contrib, branch which contains the patches to be tested. The program will auto-discover the available tests residing in this directory by looking for unittest classes, and will run the tests on the branch dumping the output to the standard output. Optionally, it can take parameters inhibiting the branch checkout, or specifying a single test to be run, for debugging purposes.
- * launcher.py is a designed to be run from a crontab or similar scheduling mechanism. It looks up a backlog file containing branches-to-test (named tasks in the source code), select the first one in FIFO manner, and launch runner.py on it. It will await for completion, and email the standard output and standard error dumps from the runner.py execution
- * recv.py is an email receiver, designed to be called as a pipe from a .forward file. It is used to monitor a mailing list, for example, and add tasks to the backlog based on review requests coming on the mailing list.
-
-
-Installation
-------------
-As prerequisite, we expect a functioning email system on a machine with Python2.
-
-The broad steps to installation
-* set up the .forward on the receiving email account to pipe to the recv.py file
-* edit config.py and settings.json to alter for local installation settings
-* on email receive, verify backlog.txt to see that the tasks are received and marked for processing
-* execute launcher.py in command line to verify that a test occurs with no problems, and that the outgoing email is delivered
-* add launcher.py
-
-
-
-Contribute
-------------
-What we need are tests. Add your own tests to either tests.py file, or to a new file.
-Use "config.logger" to write logs that will make it to email.
-
-Commonly used code should be going to shellutils, and configuration to config.py.
-
-Contribute code by emailing patches to the list: toaster@yoctoproject.org (membership required)
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/TODO b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/TODO
deleted file mode 100644
index 117192106..000000000
--- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/TODO
+++ /dev/null
@@ -1,9 +0,0 @@
-We need to implement tests:
-
-automated link checker; currently
-$ linkchecker -t 1000 -F csv http://localhost:8000/
-
-integrate the w3c-validation service; currently
-$ python urlcheck.py
-
-
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/config.py b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/config.py
deleted file mode 100644
index 87b427cc3..000000000
--- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/config.py
+++ /dev/null
@@ -1,98 +0,0 @@
-#!/usr/bin/python
-
-# ex:ts=4:sw=4:sts=4:et
-# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
-#
-# Copyright (C) 2015 Alexandru Damian for Intel Corp.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-# This is the configuration/single module for tts
-# everything that would be a global variable goes here
-
-import os, sys, logging
-import socket
-
-LOGDIR = "log"
-SETTINGS_FILE = os.path.join(os.path.dirname(__file__), "settings.json")
-TEST_DIR_NAME = "tts_testdir"
-
-DEBUG = True
-
-OWN_PID = os.getpid()
-
-W3C_VALIDATOR = "http://icarus.local/w3c-validator/check?doctype=HTML5&uri="
-
-TOASTER_PORT = 56789
-
-TESTDIR = None
-
-#we parse the w3c URL to know where to connect
-
-import urlparse
-
-def get_public_ip():
- temp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- parsed_url = urlparse.urlparse("http://icarus.local/w3c-validator/check?doctype=HTML5&uri=")
- temp_socket.connect((parsed_url.netloc, 80 if parsed_url.port is None else parsed_url.port))
- public_ip = temp_socket.getsockname()[0]
- temp_socket.close()
- return public_ip
-
-TOASTER_BASEURL = "http://%s:%d/" % (get_public_ip(), TOASTER_PORT)
-
-
-OWN_EMAIL_ADDRESS = "Toaster Testing Framework <alexandru.damian@intel.com>"
-REPORT_EMAIL_ADDRESS = "alexandru.damian@intel.com"
-
-# make sure we have the basic logging infrastructure
-
-#pylint: disable=invalid-name
-# we disable the invalid name because the module-level "logger" is used througout bitbake
-logger = logging.getLogger("toastertest")
-__console__ = logging.StreamHandler(sys.stdout)
-__console__.setFormatter(logging.Formatter("%(asctime)s %(levelname)s: %(message)s"))
-logger.addHandler(__console__)
-logger.setLevel(logging.DEBUG)
-
-
-# singleton file names
-LOCKFILE = "/tmp/ttf.lock"
-BACKLOGFILE = os.path.join(os.path.dirname(__file__), "backlog.txt")
-
-# task states
-def enum(*sequential, **named):
- enums = dict(zip(sequential, range(len(sequential))), **named)
- reverse = dict((value, key) for key, value in enums.items())
- enums['reverse_mapping'] = reverse
- return type('Enum', (), enums)
-
-
-class TASKS(object):
- #pylint: disable=too-few-public-methods
- PENDING = "PENDING"
- INPROGRESS = "INPROGRESS"
- DONE = "DONE"
-
- @staticmethod
- def next_task(task):
- if task == TASKS.PENDING:
- return TASKS.INPROGRESS
- if task == TASKS.INPROGRESS:
- return TASKS.DONE
- raise Exception("Invalid next task state for %s" % task)
-
-# TTS specific
-CONTRIB_REPO = "git@git.yoctoproject.org:poky-contrib"
-
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/launcher.py b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/launcher.py
deleted file mode 100755
index e5794c1c5..000000000
--- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/launcher.py
+++ /dev/null
@@ -1,101 +0,0 @@
-#!/usr/bin/python
-
-# ex:ts=4:sw=4:sts=4:et
-# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
-#
-# Copyright (C) 2015 Alexandru Damian for Intel Corp.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-# Program to run the next task listed from the backlog.txt; designed to be
-# run from crontab.
-
-from __future__ import print_function
-import sys, os, config, shellutils
-from shellutils import ShellCmdException
-
-# Import smtplib for the actual sending function
-import smtplib
-
-# Import the email modules we'll need
-from email.mime.text import MIMEText
-
-def _take_lockfile():
- return shellutils.lockfile(shellutils.mk_lock_filename())
-
-
-def read_next_task_by_state(task_state, task_name=None):
- if not os.path.exists(os.path.join(os.path.dirname(__file__), config.BACKLOGFILE)):
- return None
- os.rename(config.BACKLOGFILE, config.BACKLOGFILE + ".tmp")
- task = None
- with open(config.BACKLOGFILE + ".tmp", "r") as f_in:
- with open(config.BACKLOGFILE, "w") as f_out:
- for line in f_in.readlines():
- if task is None:
- fields = line.strip().split("|", 2)
- if fields[1] == task_state:
- if task_name is None or task_name == fields[0]:
- task = fields[0]
- print("Updating %s %s to %s" % (task, task_state, config.TASKS.next_task(task_state)))
- line = "%s|%s\n" % (task, config.TASKS.next_task(task_state))
- f_out.write(line)
- os.remove(config.BACKLOGFILE + ".tmp")
- return task
-
-def send_report(task_name, plaintext, errtext=None):
- if errtext is None:
- msg = MIMEText(plaintext)
- else:
- if plaintext is None:
- plaintext = ""
- msg = MIMEText("--STDOUT dump--\n\n%s\n\n--STDERR dump--\n\n%s" % (plaintext, errtext))
-
- msg['Subject'] = "[review-request] %s - smoke test results" % task_name
- msg['From'] = config.OWN_EMAIL_ADDRESS
- msg['To'] = config.REPORT_EMAIL_ADDRESS
-
- smtp_connection = smtplib.SMTP("localhost")
- smtp_connection.sendmail(config.OWN_EMAIL_ADDRESS, [config.REPORT_EMAIL_ADDRESS], msg.as_string())
- smtp_connection.quit()
-
-def main():
- # we don't do anything if we have another instance of us running
- lock_file = _take_lockfile()
-
- if lock_file is None:
- if config.DEBUG:
- print("Concurrent script in progress, exiting")
- sys.exit(1)
-
- next_task = read_next_task_by_state(config.TASKS.PENDING)
- if next_task is not None:
- print("Next task is", next_task)
- errtext = None
- out = None
- try:
- out = shellutils.run_shell_cmd("%s %s" % (os.path.join(os.path.dirname(__file__), "runner.py"), next_task))
- except ShellCmdException as exc:
- print("Failed while running the test runner: %s", exc)
- errtext = exc.__str__()
- send_report(next_task, out, errtext)
- read_next_task_by_state(config.TASKS.INPROGRESS, next_task)
- else:
- print("No task")
-
- shellutils.unlockfile(lock_file)
-
-
-if __name__ == "__main__":
- main()
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/recv.py b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/recv.py
deleted file mode 100755
index 07efdac44..000000000
--- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/recv.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/python
-
-# ex:ts=4:sw=4:sts=4:et
-# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
-#
-# Copyright (C) 2015 Alexandru Damian for Intel Corp.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-# Program to receive review requests by email and log tasks to backlog.txt
-# Designed to be run by the email system from a .forward file:
-#
-# cat .forward
-# |[full/path]/recv.py
-
-from __future__ import print_function
-import sys, config, shellutils
-
-from email.parser import Parser
-
-def recv_mail(datastring):
- headers = Parser().parsestr(datastring)
- return headers['subject']
-
-def main():
- lock_file = shellutils.lockfile(shellutils.mk_lock_filename(), retry=True)
-
- if lock_file is None:
- if config.DEBUG:
- print("Concurrent script in progress, exiting")
- sys.exit(1)
-
- subject = recv_mail(sys.stdin.read())
-
- subject_parts = subject.split()
- if "[review-request]" in subject_parts:
- task_name = subject_parts[subject_parts.index("[review-request]") + 1]
- with open(config.BACKLOGFILE, "a") as fout:
- line = "%s|%s\n" % (task_name, config.TASKS.PENDING)
- fout.write(line)
-
- shellutils.unlockfile(lock_file)
-
-if __name__ == "__main__":
- main()
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/runner.py b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/runner.py
deleted file mode 100755
index d01386acf..000000000
--- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/runner.py
+++ /dev/null
@@ -1,222 +0,0 @@
-#!/usr/bin/python
-
-# ex:ts=4:sw=4:sts=4:et
-# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
-#
-# Copyright (C) 2015 Alexandru Damian for Intel Corp.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-
-# This is the main test execution controller. It is designed to be run
-# manually from the command line, or to be called from a different program
-# that schedules test execution.
-#
-# Execute runner.py -h for help.
-
-
-
-from __future__ import print_function
-import sys, os
-import unittest, importlib
-import logging, pprint, json
-import re
-from shellutils import ShellCmdException, mkdirhier, run_shell_cmd
-
-import config
-
-# we also log to a file, in addition to console, because our output is important
-__log_file_name__ = os.path.join(os.path.dirname(__file__), "log/tts_%d.log" % config.OWN_PID)
-mkdirhier(os.path.dirname(__log_file_name__))
-__log_file__ = open(__log_file_name__, "w")
-__file_handler__ = logging.StreamHandler(__log_file__)
-__file_handler__.setFormatter(logging.Formatter("%(asctime)s %(levelname)s: %(message)s"))
-
-config.logger.addHandler(__file_handler__)
-
-# set up log directory
-try:
- if not os.path.exists(config.LOGDIR):
- os.mkdir(config.LOGDIR)
- else:
- if not os.path.isdir(config.LOGDIR):
- raise Exception("Expected log dir '%s' is not actually a directory." % config.LOGDIR)
-except OSError as exc:
- raise exc
-
-# creates the under-test-branch as a separate directory
-def set_up_test_branch(settings, branch_name):
- testdir = "%s/%s.%d" % (settings['workdir'], config.TEST_DIR_NAME, config.OWN_PID)
-
- # creates the host dir
- if os.path.exists(testdir):
- raise Exception("Test dir '%s'is already there, aborting" % testdir)
-
- # may raise OSError, is to be handled by the caller
- os.makedirs(testdir)
-
-
- # copies over the .git from the localclone
- run_shell_cmd("cp -a '%s'/.git '%s'" % (settings['localclone'], testdir))
-
- # add the remote if it doesn't exist
- crt_remotes = run_shell_cmd("git remote -v", cwd=testdir)
- remotes = [word for line in crt_remotes.split("\n") for word in line.split()]
- if not config.CONTRIB_REPO in remotes:
- remote_name = "tts_contrib"
- run_shell_cmd("git remote add %s %s" % (remote_name, config.CONTRIB_REPO), cwd=testdir)
- else:
- remote_name = remotes[remotes.index(config.CONTRIB_REPO) - 1]
-
- # do the fetch
- run_shell_cmd("git fetch %s -p" % remote_name, cwd=testdir)
-
- # do the checkout
- run_shell_cmd("git checkout origin/master && git branch -D %s; git checkout %s/%s -b %s && git reset --hard" % (branch_name, remote_name, branch_name, branch_name), cwd=testdir)
-
- return testdir
-
-
-def __search_for_tests():
- # we find all classes that can run, and run them
- tests = []
- for _, _, files_list in os.walk(os.path.dirname(os.path.abspath(__file__))):
- for module_file in [f[:-3] for f in files_list if f.endswith(".py") and not f.startswith("__init__")]:
- config.logger.debug("Inspecting module %s", module_file)
- current_module = importlib.import_module(module_file)
- crtclass_names = vars(current_module)
- for name in crtclass_names:
- tested_value = crtclass_names[name]
- if isinstance(tested_value, type(unittest.TestCase)) and issubclass(tested_value, unittest.TestCase):
- tests.append((module_file, name))
- break
- return tests
-
-
-# boilerplate to self discover tests and run them
-def execute_tests(dir_under_test, testname):
-
- if testname is not None and "." in testname:
- tests = []
- tests.append(tuple(testname.split(".", 2)))
- else:
- tests = __search_for_tests()
-
- # let's move to the directory under test
- crt_dir = os.getcwd()
- os.chdir(dir_under_test)
-
- # execute each module
- # pylint: disable=broad-except
- # we disable the broad-except because we want to actually catch all possible exceptions
- try:
- # sorting the tests by the numeric order in the class name
- tests = sorted(tests, key=lambda x: int(re.search(r"[0-9]+", x[1]).group(0)))
- config.logger.debug("Discovered test clases: %s", pprint.pformat(tests))
- unittest.installHandler()
- suite = unittest.TestSuite()
- loader = unittest.TestLoader()
- result = unittest.TestResult()
- result.failfast = True
- for module_file, test_name in tests:
- suite.addTest(loader.loadTestsFromName("%s.%s" % (module_file, test_name)))
- config.logger.info("Running %d test(s)", suite.countTestCases())
- suite.run(result)
-
- for error in result.errors:
- config.logger.error("Exception on test: %s\n%s", error[0],
- "\n".join(["-- %s" % x for x in error[1].split("\n")]))
-
- for failure in result.failures:
- config.logger.error("Failed test: %s:\n%s\n", failure[0],
- "\n".join(["-- %s" % x for x in failure[1].split("\n")]))
-
- config.logger.info("Test results: %d ran, %d errors, %d failures", result.testsRun, len(result.errors), len(result.failures))
-
- except Exception as exc:
- import traceback
- config.logger.error("Exception while running test. Tracedump: \n%s", traceback.format_exc())
- finally:
- os.chdir(crt_dir)
- return len(result.failures)
-
-# verify that we had a branch-under-test name as parameter
-def validate_args():
- from optparse import OptionParser
- parser = OptionParser(usage="usage: %prog [options] branch_under_test")
-
- parser.add_option("-t", "--test-dir", dest="testdir", default=None, help="Use specified directory to run tests, inhibits the checkout.")
- parser.add_option("-s", "--single", dest="singletest", default=None, help="Run only the specified test")
-
- (options, args) = parser.parse_args()
- if len(args) < 1:
- raise Exception("Please specify the branch to run on. Use option '-h' when in doubt.")
- return (options, args)
-
-
-
-
-# load the configuration options
-def read_settings():
- if not os.path.exists(config.SETTINGS_FILE) or not os.path.isfile(config.SETTINGS_FILE):
- raise Exception("Config file '%s' cannot be openend" % config.SETTINGS_FILE)
- return json.loads(open(config.SETTINGS_FILE, "r").read())
-
-
-# cleanup !
-def clean_up(testdir):
- run_shell_cmd("rm -rf -- '%s'" % testdir)
-
-def dump_info(settings, options, args):
- """ detailed information about current run configuration, for debugging purposes.
- """
- config.logger.debug("Settings:\n%s\nOptions:\n%s\nArguments:\n%s\n", settings, options, args)
-
-def main():
- (options, args) = validate_args()
-
- settings = read_settings()
- need_cleanup = False
-
- # dump debug info
- dump_info(settings, options, args)
-
- testdir = None
- no_failures = 1
- try:
- if options.testdir is not None and os.path.exists(options.testdir):
- testdir = os.path.abspath(options.testdir)
- config.logger.info("No checkout, using %s", testdir)
- else:
- need_cleanup = True
- testdir = set_up_test_branch(settings, args[0]) # we expect a branch name as first argument
-
- config.TESTDIR = testdir # we let tests know where to run
-
- # ensure that the test dir only contains no *.pyc leftovers
- run_shell_cmd("find '%s' -type f -name *.pyc -exec rm {} \\;" % testdir)
-
- no_failures = execute_tests(testdir, options.singletest)
-
- except ShellCmdException as exc:
- import traceback
- config.logger.error("Error while setting up testing. Traceback: \n%s", traceback.format_exc())
- finally:
- if need_cleanup and testdir is not None:
- clean_up(testdir)
-
- sys.exit(no_failures)
-
-if __name__ == "__main__":
- main()
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/settings.json b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/settings.json
deleted file mode 100644
index bb671eaf2..000000000
--- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/settings.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "repo": "git@git.yoctoproject.org:poky-contrib",
- "localclone": "/home/ddalex/ssd/yocto/poky",
- "workdir": "/home/ddalex/ssd/yocto"
-}
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/shellutils.py b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/shellutils.py
deleted file mode 100644
index ce64c0634..000000000
--- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/shellutils.py
+++ /dev/null
@@ -1,141 +0,0 @@
-#!/usr/bin/python
-
-# ex:ts=4:sw=4:sts=4:et
-# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
-#
-# Copyright (C) 2015 Alexandru Damian for Intel Corp.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-# Utilities shared by tests and other common bits of code.
-
-import sys, os, subprocess, fcntl, errno
-import config
-from config import logger
-
-
-# License warning; this code is copied from the BitBake project, file bitbake/lib/bb/utils.py
-# The code is originally licensed GPL-2.0, and we redistribute it under still GPL-2.0
-
-# End of copy is marked with #ENDOFCOPY marker
-
-def mkdirhier(directory):
- """Create a directory like 'mkdir -p', but does not complain if
- directory already exists like os.makedirs
- """
-
- try:
- os.makedirs(directory)
- except OSError as exc:
- if exc.errno != errno.EEXIST:
- raise exc
-
-def lockfile(name, shared=False, retry=True):
- """
- Use the file fn as a lock file, return when the lock has been acquired.
- Returns a variable to pass to unlockfile().
- """
- config.logger.debug("take lockfile %s", name)
- dirname = os.path.dirname(name)
- mkdirhier(dirname)
-
- if not os.access(dirname, os.W_OK):
- logger.error("Unable to acquire lock '%s', directory is not writable",
- name)
- sys.exit(1)
-
- operation = fcntl.LOCK_EX
- if shared:
- operation = fcntl.LOCK_SH
- if not retry:
- operation = operation | fcntl.LOCK_NB
-
- while True:
- # If we leave the lockfiles lying around there is no problem
- # but we should clean up after ourselves. This gives potential
- # for races though. To work around this, when we acquire the lock
- # we check the file we locked was still the lock file on disk.
- # by comparing inode numbers. If they don't match or the lockfile
- # no longer exists, we start again.
-
- # This implementation is unfair since the last person to request the
- # lock is the most likely to win it.
-
- # pylint: disable=broad-except
- # we disable the broad-except because we want to actually catch all possible exceptions
- try:
- lock_file = open(name, 'a+')
- fileno = lock_file.fileno()
- fcntl.flock(fileno, operation)
- statinfo = os.fstat(fileno)
- if os.path.exists(lock_file.name):
- statinfo2 = os.stat(lock_file.name)
- if statinfo.st_ino == statinfo2.st_ino:
- return lock_file
- lock_file.close()
- except Exception as exc:
- try:
- lock_file.close()
- except Exception as exc2:
- config.logger.error("Failed to close the lockfile: %s", exc2)
- config.logger.error("Failed to acquire the lockfile: %s", exc)
- if not retry:
- return None
-
-def unlockfile(lock_file):
- """
- Unlock a file locked using lockfile()
- """
- try:
- # If we had a shared lock, we need to promote to exclusive before
- # removing the lockfile. Attempt this, ignore failures.
- fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
- os.unlink(lock_file.name)
- except (IOError, OSError):
- pass
- fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
- lock_file.close()
-
-#ENDOFCOPY
-
-
-def mk_lock_filename():
- our_name = os.path.basename(__file__)
- our_name = ".%s" % ".".join(reversed(our_name.split(".")))
- return config.LOCKFILE + our_name
-
-
-
-class ShellCmdException(Exception):
- pass
-
-def run_shell_cmd(command, cwd=None):
- if cwd is None:
- cwd = os.getcwd()
-
- config.logger.debug("_shellcmd: (%s) %s", cwd, command)
- process = subprocess.Popen(command, cwd=cwd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- (out, err) = process.communicate()
- process.wait()
- if process.returncode:
- if len(err) == 0:
- err = "command: %s \n%s" % (command, out)
- else:
- err = "command: %s \n%s" % (command, err)
- config.logger.warning("_shellcmd: error \n%s\n%s", out, err)
- raise ShellCmdException(err)
- else:
- #config.logger.debug("localhostbecontroller: shellcmd success\n%s" % out)
- return out
-
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/tests.py b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/tests.py
deleted file mode 100644
index c510ebb10..000000000
--- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/tests.py
+++ /dev/null
@@ -1,115 +0,0 @@
-#!/usr/bin/python
-
-# ex:ts=4:sw=4:sts=4:et
-# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
-#
-# Copyright (C) 2015 Alexandru Damian for Intel Corp.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-
-# Test definitions. The runner will look for and auto-discover the tests
-# no matter what they file are they in, as long as they are in the same directory
-# as this file.
-
-import unittest
-from shellutils import run_shell_cmd, ShellCmdException
-import config
-
-import pexpect
-import sys, os, signal, time
-
-class Test00PyCompilable(unittest.TestCase):
- ''' Verifies that all Python files are syntactically correct '''
- def test_compile_file(self):
- try:
- run_shell_cmd("find . -name *py -type f -print0 | xargs -0 -n1 -P20 python -m py_compile", config.TESTDIR)
- except ShellCmdException as exc:
- self.fail("Error compiling python files: %s" % (exc))
-
- def test_pylint_file(self):
- try:
- run_shell_cmd(r"find . -iname \"*\.py\" -type f -print0 | PYTHONPATH=${PYTHONPATH}:. xargs -r -0 -n1 pylint --load-plugins pylint_django -E --reports=n 2>&1", cwd=config.TESTDIR + "/bitbake/lib/toaster")
- except ShellCmdException as exc:
- self.fail("Pylint fails: %s\n" % exc)
-
-class Test01PySystemStart(unittest.TestCase):
- ''' Attempts to start Toaster, verify that it is succesfull, and stop it '''
- def setUp(self):
- run_shell_cmd("bash -c 'rm -f build/*log'")
-
- def test_start_interactive_mode(self):
- try:
- run_shell_cmd("bash -c 'source %s/oe-init-build-env && source toaster start webport=%d && source toaster stop'" % (config.TESTDIR, config.TOASTER_PORT), config.TESTDIR)
- except ShellCmdException as exc:
- self.fail("Failed starting interactive mode: %s" % (exc))
-
- def test_start_managed_mode(self):
- try:
- run_shell_cmd("%s/bitbake/bin/toaster webport=%d nobrowser & sleep 10 && curl http://localhost:%d/ && kill -2 %%1" % (config.TESTDIR, config.TOASTER_PORT, config.TOASTER_PORT), config.TESTDIR)
- except ShellCmdException as exc:
- self.fail("Failed starting managed mode: %s" % (exc))
-
-class Test02HTML5Compliance(unittest.TestCase):
- def setUp(self):
- self.origdir = os.getcwd()
- self.crtdir = os.path.dirname(config.TESTDIR)
- self.cleanup_database = False
- os.chdir(self.crtdir)
- if not os.path.exists(os.path.join(self.crtdir, "toaster.sqlite")):
- self.cleanup_database = True
- run_shell_cmd("%s/bitbake/lib/toaster/manage.py syncdb --noinput" % config.TESTDIR)
- run_shell_cmd("%s/bitbake/lib/toaster/manage.py migrate orm" % config.TESTDIR)
- run_shell_cmd("%s/bitbake/lib/toaster/manage.py migrate bldcontrol" % config.TESTDIR)
- run_shell_cmd("%s/bitbake/lib/toaster/manage.py loadconf %s/meta-yocto/conf/toasterconf.json" % (config.TESTDIR, config.TESTDIR))
- run_shell_cmd("%s/bitbake/lib/toaster/manage.py lsupdates" % config.TESTDIR)
-
- setup = pexpect.spawn("%s/bitbake/lib/toaster/manage.py checksettings" % config.TESTDIR)
- setup.logfile = sys.stdout
- setup.expect(r".*or type the full path to a different directory: ")
- setup.sendline('')
- setup.sendline('')
- setup.expect(r".*or type the full path to a different directory: ")
- setup.sendline('')
- setup.expect(r"Enter your option: ")
- setup.sendline('0')
-
- self.child = pexpect.spawn("bash", ["%s/bitbake/bin/toaster" % config.TESTDIR, "webport=%d" % config.TOASTER_PORT, "nobrowser"], cwd=self.crtdir)
- self.child.logfile = sys.stdout
- self.child.expect("Toaster is now running. You can stop it with Ctrl-C")
-
- def test_html5_compliance(self):
- import urllist, urlcheck
- results = {}
- for url in urllist.URLS:
- results[url] = urlcheck.validate_html5(config.TOASTER_BASEURL + url)
-
- failed = []
- for url in results:
- if results[url][1] != 0:
- failed.append((url, results[url]))
-
-
- self.assertTrue(len(failed) == 0, "Not all URLs validate: \n%s " % "\n".join(["".join(str(x)) for x in failed]))
-
- #(config.TOASTER_BASEURL + url, status, errors, warnings))
-
- def tearDown(self):
- while self.child.isalive():
- self.child.kill(signal.SIGINT)
- time.sleep(1)
- os.chdir(self.origdir)
- toaster_sqlite_path = os.path.join(self.crtdir, "toaster.sqlite")
- if self.cleanup_database and os.path.exists(toaster_sqlite_path):
- os.remove(toaster_sqlite_path)
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/run_toastertests.py b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/run_toastertests.py
deleted file mode 100755
index 8ca45a813..000000000
--- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/run_toastertests.py
+++ /dev/null
@@ -1,155 +0,0 @@
-#!/usr/bin/python
-
-# Copyright
-
-# DESCRIPTION
-# This is script for running all selected toaster cases on
-# selected web browsers manifested in toaster_test.cfg.
-
-# 1. How to start toaster in yocto:
-# $ source poky/oe-init-build-env
-# $ source toaster start
-# $ bitbake core-image-minimal
-
-# 2. How to install selenium on Ubuntu:
-# $ sudo apt-get install scrot python-pip
-# $ sudo pip install selenium
-
-# 3. How to install selenium addon in firefox:
-# Download the lastest firefox addon from http://release.seleniumhq.org/selenium-ide/
-# Then install it. You can also install firebug and firepath addon
-
-# 4. How to start writing a new case:
-# All you need to do is to implement the function test_xxx() and pile it on.
-
-# 5. How to test with Chrome browser
-# Download/install chrome on host
-# Download chromedriver from https://code.google.com/p/chromedriver/downloads/list according to your host type
-# put chromedriver in PATH, (e.g. /usr/bin/, bear in mind to chmod)
-# For windows host, you may put chromedriver.exe in the same directory as chrome.exe
-
-import unittest, sys, os, platform
-import ConfigParser
-import argparse
-from toaster_automation_test import toaster_cases
-
-
-def get_args_parser():
- description = "Script that runs toaster auto tests."
- parser = argparse.ArgumentParser(description=description)
- parser.add_argument('--run-all-tests', required=False, action="store_true", dest="run_all_tests", default=False,
- help='Run all tests.')
- parser.add_argument('--run-suite', required=False, dest='run_suite', default=False,
- help='run suite (defined in cfg file)')
-
- return parser
-
-
-def get_tests():
- testslist = []
-
- prefix = 'toaster_automation_test.toaster_cases'
-
- for t in dir(toaster_cases):
- if t.startswith('test_'):
- testslist.append('.'.join((prefix, t)))
-
- return testslist
-
-
-def get_tests_from_cfg(suite=None):
-
- testslist = []
- config = ConfigParser.SafeConfigParser()
- config.read('toaster_test.cfg')
-
- if suite is not None:
- target_suite = suite.lower()
-
- # TODO: if suite is valid suite
-
- else:
- target_suite = platform.system().lower()
-
- try:
- tests_from_cfg = eval(config.get('toaster_test_' + target_suite, 'test_cases'))
- except:
- print('Failed to get test cases from cfg file. Make sure the format is correct.')
- return None
-
- prefix = 'toaster_automation_test.toaster_cases.test_'
- for t in tests_from_cfg:
- testslist.append(prefix + str(t))
-
- return testslist
-
-def main():
-
- # In case this script is called from other directory
- os.chdir(os.path.abspath(sys.path[0]))
-
- parser = get_args_parser()
- args = parser.parse_args()
-
- if args.run_all_tests:
- testslist = get_tests()
- elif args.run_suite:
- testslist = get_tests_from_cfg(args.run_suite)
- os.environ['TOASTER_SUITE'] = args.run_suite
- else:
- testslist = get_tests_from_cfg()
-
- if not testslist:
- print('Failed to get test cases.')
- exit(1)
-
- suite = unittest.TestSuite()
- loader = unittest.TestLoader()
- loader.sortTestMethodsUsing = None
- runner = unittest.TextTestRunner(verbosity=2, resultclass=buildResultClass(args))
-
- for test in testslist:
- try:
- suite.addTests(loader.loadTestsFromName(test))
- except:
- return 1
-
- result = runner.run(suite)
-
- if result.wasSuccessful():
- return 0
- else:
- return 1
-
-
-def buildResultClass(args):
- """Build a Result Class to use in the testcase execution"""
-
- class StampedResult(unittest.TextTestResult):
- """
- Custom TestResult that prints the time when a test starts. As toaster-auto
- can take a long time (ie a few hours) to run, timestamps help us understand
- what tests are taking a long time to execute.
- """
- def startTest(self, test):
- import time
- self.stream.write(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + " - ")
- super(StampedResult, self).startTest(test)
-
- return StampedResult
-
-
-if __name__ == "__main__":
-
- try:
- ret = main()
- except:
- ret = 1
- import traceback
- traceback.print_exc()
- finally:
- if os.getenv('TOASTER_SUITE'):
- del os.environ['TOASTER_SUITE']
- sys.exit(ret)
-
-
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py
deleted file mode 100755
index 1a786fa02..000000000
--- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py
+++ /dev/null
@@ -1,2376 +0,0 @@
-#!/usr/bin/python
-# Copyright
-
-# DESCRIPTION
-# This is toaster automation base class and test cases file
-
-# History:
-# 2015.03.09 inital version
-# 2015.03.23 adding toaster_test.cfg, run_toastertest.py so we can run case by case from outside
-
-# Briefs:
-# This file is comprised of 3 parts:
-# I: common utils like sorting, getting attribute.. etc
-# II: base class part, which complies with unittest frame work and
-# contains class selenium-based functions
-# III: test cases
-# to add new case: just implement new test_xxx() function in class toaster_cases
-
-# NOTES for cases:
-# case 946:
-# step 6 - 8 needs to be observed using screenshots
-# case 956:
-# step 2 - 3 needs to be run manually
-
-import unittest, time, re, sys, getopt, os, logging, string, errno, exceptions
-import shutil, argparse, ConfigParser, platform, json
-from selenium import webdriver
-from selenium.common.exceptions import NoSuchElementException
-from selenium import selenium
-from selenium.webdriver.common.by import By
-from selenium.webdriver.common.keys import Keys
-from selenium.webdriver.support.ui import Select
-import sqlite3 as sqlite
-
-
-###########################################
-# #
-# PART I: utils stuff #
-# #
-###########################################
-
-class Listattr(object):
- """
- Set of list attribute. This is used to determine what the list content is.
- Later on we may add more attributes here.
- """
- NULL = "null"
- NUMBERS = "numbers"
- STRINGS = "strings"
- PERCENT = "percentage"
- SIZE = "size"
- UNKNOWN = "unknown"
-
-
-def get_log_root_dir():
- max_depth = 5
- parent_dir = '../'
- for number in range(0, max_depth):
- if os.path.isdir(sys.path[0] + os.sep + (os.pardir + os.sep)*number + 'log'):
- log_root_dir = os.path.abspath(sys.path[0] + os.sep + (os.pardir + os.sep)*number + 'log')
- break
-
- if number == (max_depth - 1):
- print('No log dir found. Please check')
- raise Exception
-
- return log_root_dir
-
-
-def mkdir_p(dir):
- try:
- os.makedirs(dir)
- except OSError as exc:
- if exc.errno == errno.EEXIST and os.path.isdir(dir):
- pass
- else:
- raise
-
-
-def get_list_attr(testlist):
- """
- To determine the list content
- """
- if not testlist:
- return Listattr.NULL
- listtest = testlist[:]
- try:
- listtest.remove('')
- except ValueError:
- pass
- pattern_percent = re.compile(r"^([0-9])+(\.)?([0-9])*%$")
- pattern_size = re.compile(r"^([0-9])+(\.)?([0-9])*( )*(K)*(M)*(G)*B$")
- pattern_number = re.compile(r"^([0-9])+(\.)?([0-9])*$")
- def get_patterned_number(pattern, tlist):
- count = 0
- for item in tlist:
- if re.search(pattern, item):
- count += 1
- return count
- if get_patterned_number(pattern_percent, listtest) == len(listtest):
- return Listattr.PERCENT
- elif get_patterned_number(pattern_size, listtest) == len(listtest):
- return Listattr.SIZE
- elif get_patterned_number(pattern_number, listtest) == len(listtest):
- return Listattr.NUMBERS
- else:
- return Listattr.STRINGS
-
-
-def is_list_sequenced(testlist):
- """
- Function to tell if list is sequenced
- Currently we may have list made up of: Strings ; numbers ; percentage ; time; size
- Each has respective way to determine if it's sequenced.
- """
- test_list = testlist[:]
- try:
- test_list.remove('')
- except ValueError:
- pass
-
- if get_list_attr(testlist) == Listattr.NULL :
- return True
-
- elif get_list_attr(testlist) == Listattr.STRINGS :
- return (sorted(test_list) == test_list)
-
- elif get_list_attr(testlist) == Listattr.NUMBERS :
- list_number = []
- for item in test_list:
- list_number.append(eval(item))
- return (sorted(list_number) == list_number)
-
- elif get_list_attr(testlist) == Listattr.PERCENT :
- list_number = []
- for item in test_list:
- list_number.append(eval(item.strip('%')))
- return (sorted(list_number) == list_number)
-
- elif get_list_attr(testlist) == Listattr.SIZE :
- list_number = []
- # currently SIZE is splitted by space
- for item in test_list:
- if item.split()[1].upper() == "KB":
- list_number.append(1024 * eval(item.split()[0]))
- elif item.split()[1].upper() == "MB":
- list_number.append(1024 * 1024 * eval(item.split()[0]))
- elif item.split()[1].upper() == "GB":
- list_number.append(1024 * 1024 * 1024 * eval(item.split()[0]))
- else:
- list_number.append(eval(item.split()[0]))
- return (sorted(list_number) == list_number)
-
- else:
- print('Unrecognized list type, please check')
- return False
-
-
-def is_list_inverted(testlist):
- """
- Function to tell if list is inverted
- Currently we may have list made up of: Strings ; numbers ; percentage ; time; size
- Each has respective way to determine if it's inverted.
- """
- test_list = testlist[:]
- try:
- test_list.remove('')
- except ValueError:
- pass
-
- if get_list_attr(testlist) == Listattr.NULL :
- return True
-
- elif get_list_attr(testlist) == Listattr.STRINGS :
- return (sorted(test_list, reverse = True) == test_list)
-
- elif get_list_attr(testlist) == Listattr.NUMBERS :
- list_number = []
- for item in test_list:
- list_number.append(eval(item))
- return (sorted(list_number, reverse = True) == list_number)
-
- elif get_list_attr(testlist) == Listattr.PERCENT :
- list_number = []
- for item in test_list:
- list_number.append(eval(item.strip('%')))
- return (sorted(list_number, reverse = True) == list_number)
-
- elif get_list_attr(testlist) == Listattr.SIZE :
- list_number = []
- # currently SIZE is splitted by space. such as 0 B; 1 KB; 2 MB
- for item in test_list:
- if item.split()[1].upper() == "KB":
- list_number.append(1024 * eval(item.split()[0]))
- elif item.split()[1].upper() == "MB":
- list_number.append(1024 * 1024 * eval(item.split()[0]))
- elif item.split()[1].upper() == "GB":
- list_number.append(1024 * 1024 * 1024 * eval(item.split()[0]))
- else:
- list_number.append(eval(item.split()[0]))
- return (sorted(list_number, reverse = True) == list_number)
-
- else:
- print('Unrecognized list type, please check')
- return False
-
-def replace_file_content(filename, item, option):
- f = open(filename)
- lines = f.readlines()
- f.close()
- output = open(filename, 'w')
- for line in lines:
- if line.startswith(item):
- output.write(item + " = '" + option + "'\n")
- else:
- output.write(line)
- output.close()
-
-def extract_number_from_string(s):
- """
- extract the numbers in a string. return type is 'list'
- """
- return re.findall(r'([0-9]+)', s)
-
-# Below is decorator derived from toaster backend test code
-class NoParsingFilter(logging.Filter):
- def filter(self, record):
- return record.levelno == 100
-
-def LogResults(original_class):
- orig_method = original_class.run
-
- from time import strftime, gmtime
- caller = 'toaster'
- timestamp = strftime('%Y%m%d%H%M%S',gmtime())
- logfile = os.path.join(os.getcwd(),'results-'+caller+'.'+timestamp+'.log')
- linkfile = os.path.join(os.getcwd(),'results-'+caller+'.log')
-
- #rewrite the run method of unittest.TestCase to add testcase logging
- def run(self, result, *args, **kws):
- orig_method(self, result, *args, **kws)
- passed = True
- testMethod = getattr(self, self._testMethodName)
- #if test case is decorated then use it's number, else use it's name
- try:
- test_case = testMethod.test_case
- except AttributeError:
- test_case = self._testMethodName
-
- class_name = str(testMethod.im_class).split("'")[1]
-
- #create custom logging level for filtering.
- custom_log_level = 100
- logging.addLevelName(custom_log_level, 'RESULTS')
-
- def results(self, message, *args, **kws):
- if self.isEnabledFor(custom_log_level):
- self.log(custom_log_level, message, *args, **kws)
- logging.Logger.results = results
-
- logging.basicConfig(filename=logfile,
- filemode='w',
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
- datefmt='%H:%M:%S',
- level=custom_log_level)
- for handler in logging.root.handlers:
- handler.addFilter(NoParsingFilter())
- local_log = logging.getLogger(caller)
-
- #check status of tests and record it
-
- for (name, msg) in result.errors:
- if (self._testMethodName == str(name).split(' ')[0]) and (class_name in str(name).split(' ')[1]):
- local_log.results("Testcase "+str(test_case)+": ERROR")
- local_log.results("Testcase "+str(test_case)+":\n"+msg)
- passed = False
- for (name, msg) in result.failures:
- if (self._testMethodName == str(name).split(' ')[0]) and (class_name in str(name).split(' ')[1]):
- local_log.results("Testcase "+str(test_case)+": FAILED")
- local_log.results("Testcase "+str(test_case)+":\n"+msg)
- passed = False
- for (name, msg) in result.skipped:
- if (self._testMethodName == str(name).split(' ')[0]) and (class_name in str(name).split(' ')[1]):
- local_log.results("Testcase "+str(test_case)+": SKIPPED")
- passed = False
- if passed:
- local_log.results("Testcase "+str(test_case)+": PASSED")
-
- # Create symlink to the current log
- if os.path.exists(linkfile):
- os.remove(linkfile)
- os.symlink(logfile, linkfile)
-
- original_class.run = run
-
- return original_class
-
-
-###########################################
-# #
-# PART II: base class #
-# #
-###########################################
-
-@LogResults
-class toaster_cases_base(unittest.TestCase):
-
- @classmethod
- def setUpClass(cls):
- cls.log = cls.logger_create()
-
- def setUp(self):
- self.screenshot_sequence = 1
- self.verificationErrors = []
- self.accept_next_alert = True
- self.host_os = platform.system().lower()
- if os.getenv('TOASTER_SUITE'):
- self.target_suite = os.getenv('TOASTER_SUITE')
- else:
- self.target_suite = self.host_os
-
- self.parser = ConfigParser.SafeConfigParser()
- self.parser.read('toaster_test.cfg')
- self.base_url = eval(self.parser.get('toaster_test_' + self.target_suite, 'toaster_url'))
-
- # create log dir . Currently , we put log files in log/tmp. After all
- # test cases are done, move them to log/$datetime dir
- self.log_tmp_dir = os.path.abspath(sys.path[0]) + os.sep + 'log' + os.sep + 'tmp'
- try:
- mkdir_p(self.log_tmp_dir)
- except OSError :
- logging.error("%(asctime)s Cannot create tmp dir under log, please check your privilege")
- # self.log = self.logger_create()
- # driver setup
- self.setup_browser()
-
- @staticmethod
- def logger_create():
- log_file = "toaster-auto-" + time.strftime("%Y%m%d%H%M%S") + ".log"
- if os.path.exists("toaster-auto.log"): os.remove("toaster-auto.log")
- os.symlink(log_file, "toaster-auto.log")
-
- log = logging.getLogger("toaster")
- log.setLevel(logging.DEBUG)
-
- fh = logging.FileHandler(filename=log_file, mode='w')
- fh.setLevel(logging.DEBUG)
-
- ch = logging.StreamHandler(sys.stdout)
- ch.setLevel(logging.INFO)
-
- formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
- fh.setFormatter(formatter)
- ch.setFormatter(formatter)
-
- log.addHandler(fh)
- log.addHandler(ch)
-
- return log
-
-
- def setup_browser(self, *browser_path):
- self.browser = eval(self.parser.get('toaster_test_' + self.target_suite, 'test_browser'))
- print(self.browser)
- if self.browser == "firefox":
- driver = webdriver.Firefox()
- elif self.browser == "chrome":
- driver = webdriver.Chrome()
- elif self.browser == "ie":
- driver = webdriver.Ie()
- else:
- driver = None
- print("unrecognized browser type, please check")
- self.driver = driver
- self.driver.implicitly_wait(30)
- return self.driver
-
-
- def save_screenshot(self, **log_args):
- """
- This function is used to save screen either by os interface or selenium interface.
- How to use:
- self.save_screenshot(screenshot_type = 'native'/'selenium', log_sub_dir = 'xxx',
- append_name = 'stepx')
- where native means screenshot func provided by OS,
- selenium means screenshot func provided by selenium webdriver
- """
- types = [log_args.get('screenshot_type')]
- # when no screenshot_type is specified
- if types == [None]:
- types = ['native', 'selenium']
- # normally append_name is used to specify which step..
- add_name = log_args.get('append_name')
- if not add_name:
- add_name = '-'
- # normally there's no need to specify sub_dir
- sub_dir = log_args.get('log_sub_dir')
- if not sub_dir:
- # use casexxx as sub_dir name
- sub_dir = 'case' + str(self.case_no)
- for item in types:
- log_dir = self.log_tmp_dir + os.sep + sub_dir
- mkdir_p(log_dir)
- log_path = log_dir + os.sep + self.browser + '-' +\
- item + '-' + add_name + '-' + str(self.screenshot_sequence) + '.png'
- if item == 'native':
- if self.host_os == "linux":
- os.system("scrot " + log_path)
- elif self.host_os=="darwin":
- os.system("screencapture -x " + log_path)
- elif item == 'selenium':
- self.driver.get_screenshot_as_file(log_path)
- self.screenshot_sequence += 1
-
- def browser_delay(self):
- """
- currently this is a workaround for some chrome test.
- Sometimes we need a delay to accomplish some operation.
- But for firefox, mostly we don't need this.
- To be discussed
- """
- if self.browser == "chrome":
- time.sleep(1)
- return
-
-
-# these functions are not contained in WebDriver class..
- def find_element_by_text(self, string):
- return self.driver.find_element_by_xpath("//*[text()='" + string + "']")
-
-
- def find_elements_by_text(self, string):
- return self.driver.find_elements_by_xpath("//*[text()='" + string + "']")
-
-
- def find_element_by_text_in_table(self, table_id, text_string):
- """
- This is used to search some certain 'text' in certain table
- """
- try:
- table_element = self.get_table_element(table_id)
- element = table_element.find_element_by_xpath("//*[text()='" + text_string + "']")
- except NoSuchElementException as e:
- print('no element found')
- raise
- return element
-
-
- def find_element_by_link_text_in_table(self, table_id, link_text):
- """
- Assume there're multiple suitable "find_element_by_link_text".
- In this circumstance we need to specify "table".
- """
- try:
- table_element = self.get_table_element(table_id)
- element = table_element.find_element_by_link_text(link_text)
- except NoSuchElementException as e:
- print('no element found')
- raise
- return element
-
-
- def find_elements_by_link_text_in_table(self, table_id, link_text):
- """
- Search link-text in certain table. This helps to narrow down search area.
- """
- try:
- table_element = self.get_table_element(table_id)
- element_list = table_element.find_elements_by_link_text(link_text)
- except NoSuchElementException as e:
- print('no element found')
- raise
- return element_list
-
-
- def find_element_by_partial_link_text_in_table(self, table_id, link_text):
- """
- Search element by partial link text in certain table.
- """
- try:
- table_element = self.get_table_element(table_id)
- element = table_element.find_element_by_partial_link_text(link_text)
- return element
- except NoSuchElementException as e:
- print('no element found')
- raise
-
-
- def find_elements_by_partial_link_text_in_table(self, table_id, link_text):
- """
- Assume there're multiple suitable "find_partial_element_by_link_text".
- """
- try:
- table_element = self.get_table_element(table_id)
- element_list = table_element.find_elements_by_partial_link_text(link_text)
- return element_list
- except NoSuchElementException as e:
- print('no element found')
- raise
-
-
- def find_element_by_xpath_in_table(self, table_id, xpath):
- """
- This helps to narrow down search area. Especially useful when dealing with pop-up form.
- """
- try:
- table_element = self.get_table_element(table_id)
- element = table_element.find_element_by_xpath(xpath)
- except NoSuchElementException as e:
- print('no element found')
- raise
- return element
-
-
- def find_elements_by_xpath_in_table(self, table_id, xpath):
- """
- This helps to narrow down search area. Especially useful when dealing with pop-up form.
- """
- try:
- table_element = self.get_table_element(table_id)
- element_list = table_element.find_elements_by_xpath(xpath)
- except NoSuchElementException as e:
- print('no elements found')
- raise
- return element_list
-
-
- def shortest_xpath(self, pname, pvalue):
- return "//*[@" + pname + "='" + pvalue + "']"
-
-
-#usually elements in the same column are with same class name. for instance: class="outcome" .TBD
- def get_table_column_text(self, attr_name, attr_value):
- c_xpath = self.shortest_xpath(attr_name, attr_value)
- elements = self.driver.find_elements_by_xpath(c_xpath)
- c_list = []
- for element in elements:
- c_list.append(element.text)
- return c_list
-
-
- def get_table_column_text_by_column_number(self, table_id, column_number):
- c_xpath = "//*[@id='" + table_id + "']//td[" + str(column_number) + "]"
- elements = self.driver.find_elements_by_xpath(c_xpath)
- c_list = []
- for element in elements:
- c_list.append(element.text)
- return c_list
-
-
- def get_table_head_text(self, *table_id):
-#now table_id is a tuple...
- if table_id:
- thead_xpath = "//*[@id='" + table_id[0] + "']//thead//th[text()]"
- elements = self.driver.find_elements_by_xpath(thead_xpath)
- c_list = []
- for element in elements:
- if element.text:
- c_list.append(element.text)
- return c_list
-#default table on page
- else:
- return self.driver.find_element_by_xpath("//*/table/thead").text
-
-
-
- def get_table_element(self, table_id, *coordinate):
- if len(coordinate) == 0:
-#return whole-table element
- element_xpath = "//*[@id='" + table_id + "']"
- try:
- element = self.driver.find_element_by_xpath(element_xpath)
- except NoSuchElementException as e:
- raise
- return element
- row = coordinate[0]
-
- if len(coordinate) == 1:
-#return whole-row element
- element_xpath = "//*[@id='" + table_id + "']/tbody/tr[" + str(row) + "]"
- try:
- element = self.driver.find_element_by_xpath(element_xpath)
- except NoSuchElementException as e:
- return False
- return element
-#now we are looking for an element with specified X and Y
- column = coordinate[1]
-
- element_xpath = "//*[@id='" + table_id + "']/tbody/tr[" + str(row) + "]/td[" + str(column) + "]"
- try:
- element = self.driver.find_element_by_xpath(element_xpath)
- except NoSuchElementException as e:
- return False
- return element
-
-
- def get_table_data(self, table_id, row_count, column_count):
- row = 1
- Lists = []
- while row <= row_count:
- column = 1
- row_content=[]
- while column <= column_count:
- s= "//*[@id='" + table_id + "']/tbody/tr[" + str(row) +"]/td[" + str(column) + "]"
- v = self.driver.find_element_by_xpath(s).text
- row_content.append(v)
- column = column + 1
- print("row_content=",row_content)
- Lists.extend(row_content)
- print(Lists[row-1][0])
- row = row + 1
- return Lists
-
- # The is_xxx_present functions only returns True/False
- # All the log work is done in test procedure, so we can easily trace back
- # using logging
- def is_text_present (self, patterns):
- for pattern in patterns:
- if str(pattern) not in self.driver.page_source:
- print('Text "'+pattern+'" is missing')
- return False
- return True
-
-
- def is_element_present(self, how, what):
- try:
- self.driver.find_element(how, what)
- except NoSuchElementException as e:
- print('Could not find element '+str(what)+' by ' + str(how))
- return False
- return True
-
-
- def is_alert_present(self):
- try: self.driver.switch_to_alert()
- except NoAlertPresentException as e: return False
- return True
-
-
- def close_alert_and_get_its_text(self):
- try:
- alert = self.driver.switch_to_alert()
- alert_text = alert.text
- if self.accept_next_alert:
- alert.accept()
- else:
- alert.dismiss()
- return alert_text
- finally: self.accept_next_alert = True
-
-
- def get_case_number(self):
- """
- what case are we running now
- """
- funcname = sys._getframe(1).f_code.co_name
- caseno_str = funcname.strip('test_')
- try:
- caseno = int(caseno_str)
- except ValueError:
- print("get case number error! please check if func name is test_xxx")
- return False
- return caseno
-
-
- def tearDown(self):
- self.log.info(' END: CASE %s log \n\n' % str(self.case_no))
- self.driver.quit()
- self.assertEqual([], self.verificationErrors)
-
-
-###################################################################
-# #
-# PART III: test cases #
-# please refer to #
-# https://bugzilla.yoctoproject.org/tr_show_case.cgi?case_id=xxx #
-# #
-###################################################################
-
-# Note: to comply with the unittest framework, we call these test_xxx functions
-# from run_toastercases.py to avoid calling setUp() and tearDown() multiple times
-
-
-class toaster_cases(toaster_cases_base):
- ##############
- # CASE 901 #
- ##############
- def test_901(self):
- # the reason why get_case_number is not in setUp function is that
- # otherwise it returns "setUp" instead of "test_xxx"
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- # open all columns
- self.driver.find_element_by_id("edit-columns-button").click()
- # adding explicitly wait for chromedriver..-_-
- self.browser_delay()
- self.driver.find_element_by_id("started_on").click()
- self.browser_delay()
- self.driver.find_element_by_id("time").click()
- self.driver.find_element_by_id("edit-columns-button").click()
- # dict: {lint text name : actual class name}
- table_head_dict = {'Outcome':'outcome', 'Recipe':'target', 'Machine':'machine', 'Started on':'started_on', 'Completed on':'completed_on', \
- 'Errors':'errors_no', 'Warnings':'warnings_no', 'Time':'time'}
- for key in table_head_dict:
- try:
- self.driver.find_element_by_link_text(key).click()
- except Exception as e:
- self.log.error("%s cannot be found on page" % key)
- raise
- column_list = self.get_table_column_text("class", table_head_dict[key])
- # after 1st click, the list should be either sequenced or inverted, but we don't have a "default order" here
- # the point is, after another click, it should be another order
- if is_list_inverted(column_list):
- self.driver.find_element_by_link_text(key).click()
- column_list = self.get_table_column_text("class", table_head_dict[key])
- self.assertTrue(is_list_sequenced(column_list), msg=("%s column not in order" % key))
- else:
- self.assertTrue(is_list_sequenced(column_list), msg=("%s column not sequenced" % key))
- self.driver.find_element_by_link_text(key).click()
- column_list = self.get_table_column_text("class", table_head_dict[key])
- self.assertTrue(is_list_inverted(column_list), msg=("%s column not inverted" % key))
- self.log.info("case passed")
-
-
- ##############
- # CASE 902 #
- ##############
- def test_902(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- # Could add more test patterns here in the future. Also, could search some items other than target column in future..
- patterns = ["minimal", "sato"]
- for pattern in patterns:
- ori_target_column_texts = self.get_table_column_text("class", "target")
- print(ori_target_column_texts)
- self.driver.find_element_by_id("search").clear()
- self.driver.find_element_by_id("search").send_keys(pattern)
- self.driver.find_element_by_id("search-button").click()
- new_target_column_texts = self.get_table_column_text("class", "target")
- # if nothing found, we still count it as "pass"
- if new_target_column_texts:
- for text in new_target_column_texts:
- self.assertTrue(text.find(pattern), msg=("%s item doesn't exist " % pattern))
- self.driver.find_element_by_css_selector("i.icon-remove").click()
- target_column_texts = self.get_table_column_text("class", "target")
- self.assertTrue(ori_target_column_texts == target_column_texts, msg=("builds changed after operations"))
-
-
- ##############
- # CASE 903 #
- ##############
- def test_903(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- # when opening a new page, "started_on" is not displayed by default
- self.driver.find_element_by_id("edit-columns-button").click()
- # currently all the delay are for chrome driver -_-
- self.browser_delay()
- self.driver.find_element_by_id("started_on").click()
- self.driver.find_element_by_id("edit-columns-button").click()
- # step 4
- items = ["Outcome", "Completed on", "Started on"]
- for item in items:
- try:
- temp_element = self.find_element_by_text_in_table('otable', item)
- # this is how we find "filter icon" in the same level as temp_element(where "a" means clickable, "i" means icon)
- self.assertTrue(temp_element.find_element_by_xpath("..//*/a/i[@class='icon-filter filtered']"))
- except Exception as e:
- self.assertFalse(True, msg=(" %s cannot be found! %s" % (item, e)))
- raise
- # step 5-6
- temp_element = self.find_element_by_link_text_in_table('otable', 'Outcome')
- temp_element.find_element_by_xpath("..//*/a/i[@class='icon-filter filtered']").click()
- self.browser_delay()
- # the 2nd option, whatever it is
- self.driver.find_element_by_xpath("(//input[@name='filter'])[2]").click()
- # click "Apply" button
- self.driver.find_element_by_xpath("//*[@id='filter_outcome']//*[text()='Apply']").click()
- # save screen here
- time.sleep(1)
- self.save_screenshot(screenshot_type='selenium', append_name='step5')
- temp_element = self.find_element_by_link_text_in_table('otable', 'Completed on')
- temp_element.find_element_by_xpath("..//*/a/i[@class='icon-filter filtered']").click()
- self.browser_delay()
- self.driver.find_element_by_xpath("//*[@id='filter_completed_on']//*[text()='Apply']").click()
- # save screen here to compare to previous one
- # please note that for chrome driver, need a little break before saving
- # screen here -_-
- self.browser_delay()
- self.save_screenshot(screenshot_type='selenium', append_name='step6')
- self.driver.find_element_by_id("search").clear()
- self.driver.find_element_by_id("search").send_keys("core-image")
- self.driver.find_element_by_id("search-button").click()
-
-
- ##############
- # CASE 904 #
- ##############
- def test_904(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_partial_link_text("core-image").click()
- self.driver.find_element_by_link_text("Tasks").click()
- self.table_name = 'otable'
- # This is how we find the "default" rows-number!
- rows_displayed = int(Select(self.driver.find_element_by_css_selector("select.pagesize")).first_selected_option.text)
- print(rows_displayed)
- self.assertTrue(self.get_table_element(self.table_name, rows_displayed), msg=("not enough rows displayed"))
- self.assertFalse(self.get_table_element(self.table_name, rows_displayed + 1), \
- msg=("more rows displayed than expected"))
- # Search text box background text is "Search tasks"
- self.assertTrue(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search tasks']"),\
- msg=("background text doesn't exist"))
-
- self.driver.find_element_by_id("search").clear()
- self.driver.find_element_by_id("search").send_keys("busybox")
- self.driver.find_element_by_id("search-button").click()
- self.browser_delay()
- self.save_screenshot(screenshot_type='selenium', append_name='step5')
- self.driver.find_element_by_css_selector("i.icon-remove").click()
- # Save screen here
- self.save_screenshot(screenshot_type='selenium', append_name='step5_2')
- self.driver.find_element_by_id("edit-columns-button").click()
- self.driver.find_element_by_id("cpu_used").click()
- self.driver.find_element_by_id("disk_io").click()
- self.driver.find_element_by_id("recipe_version").click()
- self.driver.find_element_by_id("time_taken").click()
- self.driver.find_element_by_id("edit-columns-button").click()
- # The operation is the same as case901
- # dict: {lint text name : actual class name}
- table_head_dict = {'Order':'order', 'Recipe':'recipe_name', 'Task':'task_name', 'Executed':'executed', \
- 'Outcome':'outcome', 'Cache attempt':'cache_attempt', 'Time (secs)':'time_taken', 'CPU usage':'cpu_used', \
- 'Disk I/O (ms)':'disk_io'}
- for key in table_head_dict:
- # This is tricky here: we are doing so because there may be more than 1
- # same-name link_text in one page. So we only find element inside the table
- self.find_element_by_link_text_in_table(self.table_name, key).click()
- column_list = self.get_table_column_text("class", table_head_dict[key])
- # after 1st click, the list should be either sequenced or inverted, but we don't have a "default order" here
- # the point is, after another click, it should be another order
- # the first case is special:this means every item in column_list is the same, so
- # after one click, either sequenced or inverted will be fine
- if (is_list_inverted(column_list) and is_list_sequenced(column_list)) \
- or (not column_list) :
- self.find_element_by_link_text_in_table(self.table_name, key).click()
- column_list = self.get_table_column_text("class", table_head_dict[key])
- self.assertTrue(is_list_sequenced(column_list) or is_list_inverted(column_list), \
- msg=("%s column not in any order" % key))
- elif is_list_inverted(column_list):
- self.find_element_by_link_text_in_table(self.table_name, key).click()
- column_list = self.get_table_column_text("class", table_head_dict[key])
- self.assertTrue(is_list_sequenced(column_list), msg=("%s column not in order" % key))
- else:
- self.assertTrue(is_list_sequenced(column_list), msg=("%s column not in order" % key))
- self.find_element_by_link_text_in_table(self.table_name, key).click()
- column_list = self.get_table_column_text("class", table_head_dict[key])
- self.assertTrue(is_list_inverted(column_list), msg=("%s column not inverted" % key))
- # step 8-10
- # filter dict: {link text name : filter table name in xpath}
- filter_dict = {'Executed':'filter_executed', 'Outcome':'filter_outcome', 'Cache attempt':'filter_cache_attempt'}
- for key in filter_dict:
- temp_element = self.find_element_by_link_text_in_table(self.table_name, key)
- # find the filter icon besides it.
- # And here we must have break (1 sec) to get the popup stuff
- temp_element.find_element_by_xpath("..//*[@class='icon-filter filtered']").click()
- self.browser_delay()
- avail_options = self.driver.find_elements_by_xpath("//*[@id='" + filter_dict[key] + "']//*[@name='filter'][not(@disabled)]")
- for number in range(0, len(avail_options)):
- avail_options[number].click()
- self.browser_delay()
- # click "Apply"
- self.driver.find_element_by_xpath("//*[@id='" + filter_dict[key] + "']//*[@type='submit']").click()
- # insert screen capture here
- self.browser_delay()
- self.save_screenshot(screenshot_type='selenium', append_name='step8')
- # after the last option was clicked, we don't need operation below anymore
- if number < len(avail_options)-1:
- try:
- temp_element = self.find_element_by_link_text_in_table(self.table_name, key)
- temp_element.find_element_by_xpath("..//*[@class='icon-filter filtered']").click()
- avail_options = self.driver.find_elements_by_xpath("//*[@id='" + filter_dict[key] + "']//*[@name='filter'][not(@disabled)]")
- except:
- print("in exception")
- self.find_element_by_text("Show all tasks").click()
-# self.driver.find_element_by_xpath("//*[@id='searchform']/button[2]").click()
- temp_element = self.find_element_by_link_text_in_table(self.table_name, key)
- temp_element.find_element_by_xpath("..//*[@class='icon-filter filtered']").click()
- avail_options = self.driver.find_elements_by_xpath("//*[@id='" + filter_dict[key] + "']//*[@name='filter'][not(@disabled)]")
- self.browser_delay()
- # step 11
- for item in ['order', 'task_name', 'executed', 'outcome', 'recipe_name', 'recipe_version']:
- try:
- self.find_element_by_xpath_in_table(self.table_name, "./tbody/tr[1]/*[@class='" + item + "']/a").click()
- except NoSuchElementException as e:
- # let it go...
- print('no item in the colum' + item)
- # insert screen shot here
- self.save_screenshot(screenshot_type='selenium', append_name='step11')
- self.driver.back()
- # step 12-14
- # about test_dict: please refer to testcase 904 requirement step 12-14
- test_dict = {
- 'Time':{
- 'class':'time_taken',
- 'check_head_list':['Recipe', 'Task', 'Executed', 'Outcome', 'Time (secs)'],
- 'check_column_list':['cpu_used', 'cache_attempt', 'disk_io', 'order', 'recipe_version']
- },
- 'CPU usage':{
- 'class':'cpu_used',
- 'check_head_list':['Recipe', 'Task', 'Executed', 'Outcome', 'CPU usage'],
- 'check_column_list':['cache_attempt', 'disk_io', 'order', 'recipe_version', 'time_taken']
- },
- 'Disk I/O':{
- 'class':'disk_io',
- 'check_head_list':['Recipe', 'Task', 'Executed', 'Outcome', 'Disk I/O (ms)'],
- 'check_column_list':['cpu_used', 'cache_attempt', 'order', 'recipe_version', 'time_taken']
- }
- }
- for key in test_dict:
- self.find_element_by_partial_link_text_in_table('nav', 'core-image').click()
- self.find_element_by_link_text_in_table('nav', key).click()
- head_list = self.get_table_head_text('otable')
- for item in test_dict[key]['check_head_list']:
- self.assertTrue(item in head_list, msg=("%s not in head row" % item))
- column_list = self.get_table_column_text('class', test_dict[key]['class'])
- self.assertTrue(is_list_inverted(column_list), msg=("%s column not inverted" % key))
-
- self.driver.find_element_by_id("edit-columns-button").click()
- for item2 in test_dict[key]['check_column_list']:
- self.driver.find_element_by_id(item2).click()
- self.driver.find_element_by_id("edit-columns-button").click()
- # TBD: save screen here
-
-
- ##############
- # CASE 906 #
- ##############
- def test_906(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_link_text("core-image-minimal").click()
- self.find_element_by_link_text_in_table('nav', 'Packages').click()
- # find "bash" in first column (Packages)
- self.driver.find_element_by_xpath("//*[@id='otable']//td[1]//*[text()='bash']").click()
- # save sceen here to observe...
- # step 6
- self.driver.find_element_by_partial_link_text("Generated files").click()
- head_list = self.get_table_head_text('otable')
- for item in ['File', 'Size']:
- self.assertTrue(item in head_list, msg=("%s not in head row" % item))
- c_list = self.get_table_column_text('class', 'path')
- self.assertTrue(is_list_sequenced(c_list), msg=("column not in order"))
- # step 7
- self.driver.find_element_by_partial_link_text("Runtime dependencies").click()
- # save sceen here to observe...
- # note that here table name is not 'otable'
- head_list = self.get_table_head_text('dependencies')
- for item in ['Package', 'Version', 'Size']:
- self.assertTrue(item in head_list, msg=("%s not in head row" % item))
- c_list = self.get_table_column_text_by_column_number('dependencies', 1)
- self.assertTrue(is_list_sequenced(c_list), msg=("list not in order"))
- texts = ['Size', 'License', 'Recipe', 'Recipe version', 'Layer', \
- 'Layer commit']
- self.failUnless(self.is_text_present(texts))
-
-
- ##############
- # CASE 910 #
- ##############
- def test_910(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- image_type="core-image-minimal"
- test_package1="busybox"
- test_package2="lib"
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_link_text(image_type).click()
- self.driver.find_element_by_link_text("Recipes").click()
- self.save_screenshot(screenshot_type='selenium', append_name='step3')
-
- self.table_name = 'otable'
- # This is how we find the "default" rows-number!
- rows_displayed = int(Select(self.driver.find_element_by_css_selector("select.pagesize")).first_selected_option.text)
- print(rows_displayed)
- self.assertTrue(self.get_table_element(self.table_name, rows_displayed))
- self.assertFalse(self.get_table_element(self.table_name, rows_displayed + 1))
-
- # Check the default table is sorted by Recipe
- tasks_column_count = len(self.driver.find_elements_by_xpath("/html/body/div[2]/div/div[2]/div[2]/table/tbody/tr/td[1]"))
- print(tasks_column_count)
- default_column_list = self.get_table_column_text_by_column_number(self.table_name, 1)
- #print default_column_list
-
- self.assertTrue(is_list_sequenced(default_column_list))
-
- # Search text box background text is "Search recipes"
- self.assertTrue(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search recipes']"))
-
- self.driver.find_element_by_id("search").clear()
- self.driver.find_element_by_id("search").send_keys(test_package1)
- self.driver.find_element_by_id("search-button").click()
- # Save screen here
- self.save_screenshot(screenshot_type='selenium', append_name='step4')
- self.driver.find_element_by_css_selector("i.icon-remove").click()
- self.save_screenshot(screenshot_type='selenium', append_name='step4_2')
-
- self.driver.find_element_by_id("edit-columns-button").click()
- self.driver.find_element_by_id("depends_on").click()
- self.driver.find_element_by_id("layer_version__branch").click()
- self.driver.find_element_by_id("layer_version__layer__commit").click()
- self.driver.find_element_by_id("depends_by").click()
- self.driver.find_element_by_id("edit-columns-button").click()
-
- self.find_element_by_link_text_in_table(self.table_name, 'Recipe').click()
- # Check the inverted table by Recipe
- # Recipe doesn't have class name
- #inverted_tasks_column_count = len(self.driver.find_elements_by_xpath("/html/body/div[2]/div/div[2]/div[2]/table/tbody/tr/td[1]"))
- #print inverted_tasks_column_count
- #inverted_column_list = self.get_table_column_text_by_column_number(self.table_name, 1)
- #print inverted_column_list
-
- #self.driver.find_element_by_partial_link_text("zlib").click()
- #self.driver.back()
- #self.assertTrue(is_list_inverted(inverted_column_list))
- #self.find_element_by_link_text_in_table(self.table_name, 'Recipe').click()
-
- table_head_dict = {'Recipe':'recipe__name', 'Recipe file':'recipe_file', 'Section':'recipe_section', \
- 'License':'recipe_license', 'Layer':'layer_version__layer__name', \
- 'Layer branch':'layer_version__branch'}
- for key in table_head_dict:
- self.find_element_by_link_text_in_table(self.table_name, key).click()
- column_list = self.get_table_column_text("class", table_head_dict[key])
- if (is_list_inverted(column_list) and is_list_sequenced(column_list)) \
- or (not column_list) :
- self.find_element_by_link_text_in_table(self.table_name, key).click()
- column_list = self.get_table_column_text("class", table_head_dict[key])
- self.assertTrue(is_list_sequenced(column_list) or is_list_inverted(column_list))
- self.driver.find_element_by_partial_link_text("acl").click()
- self.driver.back()
- self.assertTrue(is_list_sequenced(column_list) or is_list_inverted(column_list))
- # Search text box background text is "Search recipes"
- self.assertTrue(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search recipes']"))
- self.driver.find_element_by_id("search").clear()
- self.driver.find_element_by_id("search").send_keys(test_package2)
- self.driver.find_element_by_id("search-button").click()
- column_search_list = self.get_table_column_text("class", table_head_dict[key])
- self.assertTrue(is_list_sequenced(column_search_list) or is_list_inverted(column_search_list))
- self.driver.find_element_by_css_selector("i.icon-remove").click()
- elif is_list_inverted(column_list):
- self.find_element_by_link_text_in_table(self.table_name, key).click()
- column_list = self.get_table_column_text("class", table_head_dict[key])
- self.assertTrue(is_list_sequenced(column_list))
- self.driver.find_element_by_partial_link_text("acl").click()
- self.driver.back()
- self.assertTrue(is_list_sequenced(column_list))
- # Search text box background text is "Search recipes"
- self.assertTrue(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search recipes']"))
- self.driver.find_element_by_id("search").clear()
- self.driver.find_element_by_id("search").send_keys(test_package2)
- self.driver.find_element_by_id("search-button").click()
- column_search_list = self.get_table_column_text("class", table_head_dict[key])
- self.assertTrue(is_list_sequenced(column_search_list))
- self.driver.find_element_by_css_selector("i.icon-remove").click()
- else:
- self.assertTrue(is_list_sequenced(column_list), msg=("list %s not sequenced" % key))
- self.find_element_by_link_text_in_table(self.table_name, key).click()
- column_list = self.get_table_column_text("class", table_head_dict[key])
- self.assertTrue(is_list_inverted(column_list))
- try:
- self.driver.find_element_by_partial_link_text("acl").click()
- except:
- self.driver.find_element_by_partial_link_text("zlib").click()
- self.driver.back()
- self.assertTrue(is_list_inverted(column_list))
- # Search text box background text is "Search recipes"
- self.assertTrue(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search recipes']"))
- self.driver.find_element_by_id("search").clear()
- self.driver.find_element_by_id("search").send_keys(test_package2)
- self.driver.find_element_by_id("search-button").click()
- column_search_list = self.get_table_column_text("class", table_head_dict[key])
- #print column_search_list
- self.assertTrue(is_list_inverted(column_search_list))
- self.driver.find_element_by_css_selector("i.icon-remove").click()
-
- # Bug 5919
- for key in table_head_dict:
- print(key)
- self.find_element_by_link_text_in_table(self.table_name, key).click()
- self.driver.find_element_by_id("edit-columns-button").click()
- self.driver.find_element_by_id(table_head_dict[key]).click()
- self.driver.find_element_by_id("edit-columns-button").click()
- self.browser_delay()
- # After hide the column, the default table should be sorted by Recipe
- tasks_column_count = len(self.driver.find_elements_by_partial_link_text("acl"))
- #print tasks_column_count
- default_column_list = self.get_table_column_text_by_column_number(self.table_name, 1)
- #print default_column_list
- self.assertTrue(is_list_sequenced(default_column_list))
-
- self.driver.find_element_by_id("edit-columns-button").click()
- self.driver.find_element_by_id("recipe_file").click()
- self.driver.find_element_by_id("recipe_section").click()
- self.driver.find_element_by_id("recipe_license").click()
- self.driver.find_element_by_id("layer_version__layer__name").click()
- self.driver.find_element_by_id("edit-columns-button").click()
-
-
- ##############
- # CASE 911 #
- ##############
- def test_911(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_link_text("core-image-minimal").click()
- self.find_element_by_link_text_in_table('nav', 'Recipes').click()
- # step 3-5
- self.driver.find_element_by_id("search").clear()
- self.driver.find_element_by_id("search").send_keys("lib")
- self.driver.find_element_by_id("search-button").click()
- # save screen here for observation
- self.save_screenshot(screenshot_type='selenium', append_name='step5')
- # step 6
- self.driver.find_element_by_css_selector("i.icon-remove").click()
- self.driver.find_element_by_id("search").clear()
- # we deliberately want "no result" here
- self.driver.find_element_by_id("search").send_keys("no such input")
- self.driver.find_element_by_id("search-button").click()
- try:
- self.find_element_by_text("Show all recipes").click()
- except:
- self.fail(msg='Could not identify blank page elements')
-
- ##############
- # CASE 912 #
- ##############
- def test_912(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_link_text("core-image-minimal").click()
- self.find_element_by_link_text_in_table('nav', 'Recipes').click()
- # step 3
- head_list = self.get_table_head_text('otable')
- for item in ['Recipe', 'Recipe version', 'Recipe file', 'Section', 'License', 'Layer']:
- self.assertTrue(item in head_list, msg=("item %s not in head row" % item))
- self.driver.find_element_by_id("edit-columns-button").click()
- self.driver.find_element_by_id("depends_on").click()
- self.driver.find_element_by_id("layer_version__branch").click()
- self.driver.find_element_by_id("layer_version__layer__commit").click()
- self.driver.find_element_by_id("depends_by").click()
- self.driver.find_element_by_id("edit-columns-button").click()
- # check if columns selected above is shown
- check_list = ['Dependencies', 'Layer branch', 'Layer commit', 'Reverse dependencies']
- head_list = self.get_table_head_text('otable')
- time.sleep(2)
- print(head_list)
- for item in check_list:
- self.assertTrue(item in head_list, msg=("item %s not in head row" % item))
- # un-check 'em all
- self.driver.find_element_by_id("edit-columns-button").click()
- self.driver.find_element_by_id("depends_on").click()
- self.driver.find_element_by_id("layer_version__branch").click()
- self.driver.find_element_by_id("layer_version__layer__commit").click()
- self.driver.find_element_by_id("depends_by").click()
- self.driver.find_element_by_id("edit-columns-button").click()
- # don't exist any more
- head_list = self.get_table_head_text('otable')
- for item in check_list:
- self.assertFalse(item in head_list, msg=("item %s should not be in head row" % item))
-
-
- ##############
- # CASE 913 #
- ##############
- def test_913(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_link_text("core-image-minimal").click()
- self.find_element_by_link_text_in_table('nav', 'Recipes').click()
- # step 3
- head_list = self.get_table_head_text('otable')
- for item in ['Recipe', 'Recipe version', 'Recipe file', 'Section', 'License', 'Layer']:
- self.assertTrue(item in head_list, msg=("item %s not in head row" % item))
- # step 4
- self.driver.find_element_by_id("edit-columns-button").click()
- # save screen
- self.browser_delay()
- self.save_screenshot(screenshot_type='selenium', append_name='step4')
- self.driver.find_element_by_id("edit-columns-button").click()
-
-
- ##############
- # CASE 914 #
- ##############
- def test_914(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- image_type="core-image-minimal"
- test_package1="busybox"
- test_package2="gdbm"
- test_package3="gettext-native"
- driver = self.driver
- driver.maximize_window()
- driver.get(self.base_url)
- driver.find_element_by_link_text(image_type).click()
- driver.find_element_by_link_text("Recipes").click()
- driver.find_element_by_link_text(test_package1).click()
-
- self.table_name = 'information'
-
- tasks_row_count = len(driver.find_elements_by_xpath("//*[@id='"+self.table_name+"']/table/tbody/tr/td[1]"))
- tasks_column_count = len(driver.find_elements_by_xpath("//*[@id='"+self.table_name+"']/table/tbody/tr[1]/td"))
- print('rows: '+str(tasks_row_count))
- print('columns: '+str(tasks_column_count))
-
- Tasks_column = self.get_table_column_text_by_column_number(self.table_name, 2)
- print ("Tasks_column=", Tasks_column)
-
- key_tasks=["do_fetch", "do_unpack", "do_patch", "do_configure", "do_compile", "do_install", "do_package", "do_build"]
- i = 0
- while i < len(key_tasks):
- if key_tasks[i] not in Tasks_column:
- print ("Error! Missing key task: %s" % key_tasks[i])
- else:
- print ("%s is in tasks" % key_tasks[i])
- i = i + 1
-
- if Tasks_column.index(key_tasks[0]) != 0:
- print ("Error! %s is not in the right position" % key_tasks[0])
- else:
- print ("%s is in right position" % key_tasks[0])
-
- if Tasks_column[-1] != key_tasks[-1]:
- print ("Error! %s is not in the right position" % key_tasks[-1])
- else:
- print ("%s is in right position" % key_tasks[-1])
-
- driver.find_element_by_partial_link_text("Packages (").click()
- packages_name = driver.find_element_by_partial_link_text("Packages (").text
- print(packages_name)
- packages_num = int(filter(str.isdigit, repr(packages_name)))
- print(packages_num)
-
- #switch the table to show more than 10 rows at a time
- self.driver.find_element_by_xpath("//*[@id='packages-built']/div[1]/div/select").click()
- Select(driver.find_element_by_xpath("//*[@id='packages-built']/div[1]/div/select")).select_by_value('150')
- self.driver.find_element_by_xpath("//*[@id='packages-built']/div[1]/div/select").send_keys(Keys.ENTER)
-
- packages_row_count = len(driver.find_elements_by_xpath("//*[@id='otable']/tbody/tr/td[1]"))
- print(packages_row_count)
-
- if packages_num != packages_row_count:
- print ("Error! The packages number is not correct")
- else:
- print ("The packages number is correct")
-
- driver.find_element_by_partial_link_text("Build dependencies (").click()
- depends_name = driver.find_element_by_partial_link_text("Build dependencies (").text
- print(depends_name)
- depends_num = int(list(filter(str.isdigit, repr(depends_name))))
- print(depends_num)
-
- if depends_num == 0:
- depends_message = repr(driver.find_element_by_css_selector("div.alert.alert-info").text)
- print(depends_message)
- if depends_message.find("has no build dependencies.") < 0:
- print ("Error! The message isn't expected.")
- else:
- print ("The message is expected")
- else:
- depends_row_count = len(driver.find_elements_by_xpath("//*[@id='dependencies']/table/tbody/tr/td[1]"))
- print(depends_row_count)
- if depends_num != depends_row_count:
- print ("Error! The dependent packages number is not correct")
- else:
- print ("The dependent packages number is correct")
-
- driver.find_element_by_partial_link_text("Reverse build dependencies (").click()
- rdepends_name = driver.find_element_by_partial_link_text("Reverse build dependencies (").text
- print(rdepends_name)
- rdepends_num = int(filter(str.isdigit, repr(rdepends_name)))
- print(rdepends_num)
-
- if rdepends_num == 0:
- rdepends_message = repr(driver.find_element_by_css_selector("#brought-in-by > div.alert.alert-info").text)
- print(rdepends_message)
- if rdepends_message.find("has no reverse build dependencies.") < 0:
- print ("Error! The message isn't expected.")
- else:
- print ("The message is expected")
- else:
- print ("The reverse dependent packages number is correct")
-
- driver.find_element_by_link_text("Recipes").click()
- driver.find_element_by_link_text(test_package2).click()
- driver.find_element_by_partial_link_text("Packages (").click()
- driver.find_element_by_partial_link_text("Build dependencies (").click()
- driver.find_element_by_partial_link_text("Reverse build dependencies (").click()
-
-
- driver.find_element_by_link_text("Recipes").click()
- driver.find_element_by_link_text(test_package3).click()
-
- native_tasks_row_count = len(driver.find_elements_by_xpath("//*[@id='information']/table/tbody/tr/td[1]"))
- native_tasks_column_count = len(driver.find_elements_by_xpath("//*[@id='information']/table/tbody/tr[1]/td"))
- print(native_tasks_row_count)
- print(native_tasks_column_count)
-
- Native_Tasks_column = self.get_table_column_text_by_column_number(self.table_name, 2)
- print ("Native_Tasks_column=", Native_Tasks_column)
-
- native_key_tasks=["do_fetch", "do_unpack", "do_patch", "do_configure", "do_compile", "do_install", "do_build"]
- i = 0
- while i < len(native_key_tasks):
- if native_key_tasks[i] not in Native_Tasks_column:
- print ("Error! Missing key task: %s" % native_key_tasks[i])
- else:
- print ("%s is in tasks" % native_key_tasks[i])
- i = i + 1
-
- if Native_Tasks_column.index(native_key_tasks[0]) != 0:
- print ("Error! %s is not in the right position" % native_key_tasks[0])
- else:
- print ("%s is in right position" % native_key_tasks[0])
-
- if Native_Tasks_column[-1] != native_key_tasks[-1]:
- print ("Error! %s is not in the right position" % native_key_tasks[-1])
- else:
- print ("%s is in right position" % native_key_tasks[-1])
-
- driver.find_element_by_partial_link_text("Packages (").click()
- native_packages_name = driver.find_element_by_partial_link_text("Packages (").text
- print(native_packages_name)
- native_packages_num = int(filter(str.isdigit, repr(native_packages_name)))
- print(native_packages_num)
-
- if native_packages_num != 0:
- print ("Error! Native task shouldn't have any packages.")
- else:
- native_package_message = repr(driver.find_element_by_css_selector("#packages-built > div.alert.alert-info").text)
- print(native_package_message)
- if native_package_message.find("does not build any packages.") < 0:
- print ("Error! The message for native task isn't expected.")
- else:
- print ("The message for native task is expected.")
-
- driver.find_element_by_partial_link_text("Build dependencies (").click()
- native_depends_name = driver.find_element_by_partial_link_text("Build dependencies (").text
- print(native_depends_name)
- native_depends_num = int(filter(str.isdigit, repr(native_depends_name)))
- print(native_depends_num)
-
- native_depends_row_count = len(driver.find_elements_by_xpath("//*[@id='dependencies']/table/tbody/tr/td[1]"))
- print(native_depends_row_count)
-
- if native_depends_num != native_depends_row_count:
- print ("Error! The dependent packages number is not correct")
- else:
- print ("The dependent packages number is correct")
-
- driver.find_element_by_partial_link_text("Reverse build dependencies (").click()
- native_rdepends_name = driver.find_element_by_partial_link_text("Reverse build dependencies (").text
- print(native_rdepends_name)
- native_rdepends_num = int(filter(str.isdigit, repr(native_rdepends_name)))
- print(native_rdepends_num)
-
- native_rdepends_row_count = len(driver.find_elements_by_xpath("//*[@id='brought-in-by']/table/tbody/tr/td[1]"))
- print(native_rdepends_row_count)
-
- if native_rdepends_num != native_rdepends_row_count:
- print ("Error! The reverse dependent packages number is not correct")
- else:
- print ("The reverse dependent packages number is correct")
-
- driver.find_element_by_link_text("Recipes").click()
-
-
- ##############
- # CASE 915 #
- ##############
- def test_915(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_link_text("core-image-minimal").click()
- # step 3
- self.find_element_by_link_text_in_table('nav', 'Configuration').click()
- self.driver.find_element_by_link_text("BitBake variables").click()
- # step 4
- self.driver.find_element_by_id("search").clear()
- self.driver.find_element_by_id("search").send_keys("lib")
- self.driver.find_element_by_id("search-button").click()
- # save screen to see result
- self.browser_delay()
- self.save_screenshot(screenshot_type='selenium', append_name='step4')
- # step 5
- self.driver.find_element_by_css_selector("i.icon-remove").click()
- head_list = self.get_table_head_text('otable')
- print(head_list)
- print(len(head_list))
- self.assertTrue(head_list == ['Variable', 'Value', 'Set in file', 'Description'], \
- msg=("head row contents wrong"))
- # step 8
- # search other string. and click "Variable" to re-sort, check if table
- # head is still the same
- self.driver.find_element_by_id("search").clear()
- self.driver.find_element_by_id("search").send_keys("poky")
- self.driver.find_element_by_id("search-button").click()
- self.find_element_by_link_text_in_table('otable', 'Variable').click()
- head_list = self.get_table_head_text('otable')
- self.assertTrue(head_list == ['Variable', 'Value', 'Set in file', 'Description'], \
- msg=("head row contents wrong"))
- self.find_element_by_link_text_in_table('otable', 'Variable').click()
- head_list = self.get_table_head_text('otable')
- self.assertTrue(head_list == ['Variable', 'Value', 'Set in file', 'Description'], \
- msg=("head row contents wrong"))
-
-
- ##############
- # CASE 916 #
- ##############
- def test_916(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_link_text("core-image-minimal").click()
- # step 2-3
- self.find_element_by_link_text_in_table('nav', 'Configuration').click()
- self.driver.find_element_by_link_text("BitBake variables").click()
- variable_list = self.get_table_column_text('class', 'variable_name')
- self.assertTrue(is_list_sequenced(variable_list), msg=("list not in order"))
- # step 4
- self.find_element_by_link_text_in_table('otable', 'Variable').click()
- variable_list = self.get_table_column_text('class', 'variable_name')
- self.assertTrue(is_list_inverted(variable_list), msg=("list not inverted"))
- self.find_element_by_link_text_in_table('otable', 'Variable').click()
- # step 5
- # searching won't change the sequentiality
- self.driver.find_element_by_id("search").clear()
- self.driver.find_element_by_id("search").send_keys("lib")
- self.driver.find_element_by_id("search-button").click()
- variable_list = self.get_table_column_text('class', 'variable_name')
- self.assertTrue(is_list_sequenced(variable_list), msg=("list not in order"))
-
-
- ##############
- # CASE 923 #
- ##############
- def test_923(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- # Step 2
- # default sequence in "Completed on" column is inverted
- c_list = self.get_table_column_text('class', 'completed_on')
- self.assertTrue(is_list_inverted(c_list), msg=("list not inverted"))
- # step 3
- self.driver.find_element_by_id("edit-columns-button").click()
- self.driver.find_element_by_id("started_on").click()
- self.driver.find_element_by_id("time").click()
- self.driver.find_element_by_id("edit-columns-button").click()
- head_list = self.get_table_head_text('otable')
- for item in ['Outcome', 'Recipe', 'Machine', 'Started on', 'Completed on', 'Failed tasks', 'Errors', 'Warnings', 'Time', "Image files", "Project"]:
- self.failUnless(item in head_list, msg=item+' is missing from table head.')
-
-
- ##############
- # CASE 924 #
- ##############
- def test_924(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- # Please refer to case 924 requirement
- # default sequence in "Completed on" column is inverted
- c_list = self.get_table_column_text('class', 'completed_on')
- self.assertTrue(is_list_inverted(c_list), msg=("list not inverted"))
- # Step 4
- # click Errors , order in "Completed on" should be disturbed. Then hide
- # error column to check if order in "Completed on" can be restored
-#THIS TEST IS NO LONGER VALID DUE TO DESIGN CHANGES. LEAVING IN PENDING UPDATES TO DESIGN
- #self.find_element_by_link_text_in_table('otable', 'Errors').click()
- #self.driver.find_element_by_id("edit-columns-button").click()
- #self.driver.find_element_by_id("errors_no").click()
- #self.driver.find_element_by_id("edit-columns-button").click()
- # Note: without time.sleep here, there'll be unpredictable error..TBD
- time.sleep(1)
- c_list = self.get_table_column_text('class', 'completed_on')
- self.assertTrue(is_list_inverted(c_list), msg=("list not inverted"))
-
-
- ##############
- # CASE 940 #
- ##############
- def test_940(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_link_text("core-image-minimal").click()
- # Step 2-3
- self.find_element_by_link_text_in_table('nav', 'Packages').click()
- check_head_list = ['Package', 'Package version', 'Size', 'Recipe']
- head_list = self.get_table_head_text('otable')
- self.assertTrue(head_list == check_head_list, msg=("head row not as expected"))
- # Step 4
- # pulldown menu
- option_ids = ['recipe__layer_version__layer__name', 'recipe__layer_version__branch', \
- 'recipe__layer_version__layer__commit', 'license', 'recipe__version']
- self.driver.find_element_by_id("edit-columns-button").click()
- for item in option_ids:
- if not self.driver.find_element_by_id(item).is_selected():
- self.driver.find_element_by_id(item).click()
- self.driver.find_element_by_id("edit-columns-button").click()
- # save screen here to observe that 'Package' and 'Package version' is
- # not selectable
- self.browser_delay()
- self.save_screenshot(screenshot_type='selenium', append_name='step4')
-
-
- ##############
- # CASE 941 #
- ##############
- def test_941(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_link_text("core-image-minimal").click()
- # Step 2-3
- self.find_element_by_link_text_in_table('nav', 'Packages').click()
- # column -- Package
- column_list = self.get_table_column_text_by_column_number('otable', 1)
- self.assertTrue(is_list_sequenced(column_list), msg=("list not in order"))
- self.find_element_by_link_text_in_table('otable', 'Size').click()
-
-
- ##############
- # CASE 942 #
- ##############
- def test_942(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_link_text("core-image-minimal").click()
- self.driver.find_element_by_link_text("Packages").click()
- #get initial table header
- head_list = self.get_table_head_text('otable')
- #remove the Recipe column from table header
- self.driver.find_element_by_id("edit-columns-button").click()
- self.driver.find_element_by_id("recipe__name").click()
- self.driver.find_element_by_id("edit-columns-button").click()
- #get modified table header
- new_head = self.get_table_head_text('otable')
- self.assertTrue(head_list > new_head)
-
- ##############
- # CASE 943 #
- ##############
- def test_943(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_link_text("core-image-minimal").click()
- self.driver.find_element_by_link_text("Packages").click()
- #search for the "bash" package -> this should definitely be present
- self.driver.find_element_by_id("search").clear()
- self.driver.find_element_by_id("search").send_keys("bash")
- self.driver.find_element_by_id("search-button").click()
- #check for the search result message "XX packages found"
- self.assertTrue(self.is_text_present("packages found"), msg=("no packages found text"))
-
-
- ##############
- # CASE 944 #
- ##############
- def test_944(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_link_text("core-image-minimal").click()
- # step 1: test Recipes page stuff
- self.driver.find_element_by_link_text("Recipes").click()
- # for these 3 items, default status is not-checked
- self.driver.find_element_by_id("edit-columns-button").click()
- self.driver.find_element_by_id("layer_version__branch").click()
- self.driver.find_element_by_id("layer_version__layer__commit").click()
- self.driver.find_element_by_id("edit-columns-button").click()
- # otable is the recipes table here
- otable_head_text = self.get_table_head_text('otable')
- for item in ["Layer", "Layer branch", "Layer commit"]:
- self.failIf(item not in otable_head_text, msg=item+' not in table head.')
- # click the fist recipe, whatever it is
- self.get_table_element("otable", 1, 1).click()
- self.assertTrue(self.is_text_present(["Layer", "Layer branch", "Layer commit", "Recipe file"]), \
- msg=("text not in web page"))
-
- # step 2: test Packages page stuff. almost same as above
- self.driver.back()
- self.browser_delay()
- self.driver.find_element_by_link_text("Packages").click()
- self.driver.find_element_by_id("edit-columns-button").click()
- self.driver.find_element_by_id("recipe__layer_version__layer__name").click()
- self.driver.find_element_by_id("recipe__layer_version__branch").click()
- self.driver.find_element_by_id("recipe__layer_version__layer__commit").click()
- self.driver.find_element_by_id("edit-columns-button").click()
- otable_head_text = self.get_table_head_text("otable")
- for item in ["Layer", "Layer branch", "Layer commit"]:
- self.assertFalse(item not in otable_head_text, msg=("item %s should be in head row" % item))
- # click the fist recipe, whatever it is
- self.get_table_element("otable", 1, 1).click()
- self.assertTrue(self.is_text_present(["Layer", "Layer branch", "Layer commit"]), \
- msg=("text not in web page"))
-
- # step 3: test Packages core-image-minimal(images) stuff. almost same as above. Note when future element-id changes...
- self.driver.back()
- self.driver.find_element_by_link_text("core-image-minimal").click()
- self.driver.find_element_by_id("edit-columns-button").click()
- self.driver.find_element_by_id("layer_name").click()
- self.driver.find_element_by_id("layer_branch").click()
- self.driver.find_element_by_id("layer_commit").click()
- self.driver.find_element_by_id("edit-columns-button").click()
- otable_head_text = self.get_table_head_text("otable")
- for item in ["Layer", "Layer branch", "Layer commit"]:
- self.assertFalse(item not in otable_head_text, msg=("item %s should be in head row" % item))
- # click the fist recipe, whatever it is
- self.get_table_element("otable", 1, 1).click()
- self.assertTrue(self.is_text_present(["Layer", "Layer branch", "Layer commit"]), \
- msg=("text not in web page"))
-
- # step 4: check Configuration page
- self.driver.back()
- self.driver.find_element_by_link_text("Configuration").click()
- otable_head_text = self.get_table_head_text()
- self.assertTrue(self.is_text_present(["Layer", "Layer branch", "Layer commit"]), \
- msg=("text not in web page"))
-
-
- ##############
- # CASE 945 #
- ##############
- def test_945(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- for item in ["Packages", "Recipes", "Tasks"]:
- self.driver.get(self.base_url)
- self.driver.find_element_by_link_text("core-image-minimal").click()
- self.driver.find_element_by_link_text(items).click()
-
- # this may be page specific. If future page content changes, try to replace it with new xpath
- xpath_showrows = "/html/body/div[4]/div/div/div[2]/div[2]/div[2]/div/div/div[2]/select"
- xpath_table = "html/body/div[4]/div/div/div[2]/div[2]/table/tbody"#"id=('otable')/tbody"
- self.driver.find_element_by_xpath(xpath_showrows).click()
- rows_displayed = int(self.driver.find_element_by_xpath(xpath_showrows + "/option[2]").text)
-
- # not sure if this is a Selenium Select bug: If page is not refreshed here, "select(by visible text)" operation will go back to 100-row page
- # Sure we can use driver.get(url) to refresh page, but since page will vary, we use click link text here
- self.driver.find_element_by_link_text(items).click()
- Select(self.driver.find_element_by_css_selector("select.pagesize")).select_by_visible_text(str(rows_displayed))
- self.failUnless(self.is_element_present(By.XPATH, xpath_table + "/tr[" + str(rows_displayed) +"]"))
- self.failIf(self.is_element_present(By.XPATH, xpath_table + "/tr[" + str(rows_displayed+1) +"]"))
-
- # click 1st package, then go back to check if it's still those rows shown.
- self.driver.find_element_by_xpath(xpath_otable + "/tr[1]/td[1]/a").click()
- time.sleep(3)
- self.driver.find_element_by_link_text(item).click()
- self.assertTrue(self.is_element_present(By.XPATH, xpath_otable + "/tr[" + str(option_tobeselected) +"]"),\
- msg=("Row %d should exist" %option_tobeselected))
- self.assertFalse(self.is_element_present(By.XPATH, xpath_otable + "/tr[" + str(option_tobeselected+1) +"]"),\
- msg=("Row %d should not exist" %(option_tobeselected+1)))
-
-
-
- ##############
- # CASE 946 #
- ##############
- def test_946(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_link_text("core-image-minimal").click()
- self.driver.find_element_by_link_text("Configuration").click()
- # step 3-4
- check_list = ["Summary", "BitBake variables"]
- for item in check_list:
- if not self.is_element_present(how=By.LINK_TEXT, what=item):
- self.log.error("%s not found" %item)
- if not self.is_text_present(['Layers', 'Layer', 'Layer branch', 'Layer commit']):
- self.log.error("text not found")
- # step 5
- self.driver.find_element_by_link_text("BitBake variables").click()
- if not self.is_text_present(['Variable', 'Value', 'Set in file', 'Description']):
- self.log.error("text not found")
- # This may be unstable because it's page-specific
- # step 6: this is how we find filter beside "Set in file"
- temp_element = self.find_element_by_text_in_table('otable', "Set in file")
- temp_element.find_element_by_xpath("..//*/a/i[@class='icon-filter filtered']").click()
- self.browser_delay()
- self.driver.find_element_by_xpath("(//input[@name='filter'])[3]").click()
- btns = self.driver.find_elements_by_css_selector("button.btn.btn-primary")
- for btn in btns:
- try:
- btn.click()
- break
- except:
- pass
- # save screen here
- self.browser_delay()
- self.save_screenshot(screenshot_type='selenium', append_name='step6')
- self.driver.find_element_by_id("edit-columns-button").click()
- # save screen here
- # step 7
- # we should manually check the step 6-8 result using screenshot
- self.browser_delay()
- self.save_screenshot(screenshot_type='selenium', append_name='step7')
- self.driver.find_element_by_id("edit-columns-button").click()
- # step 9
- # click the 1st item, no matter what it is
- self.driver.find_element_by_xpath("//*[@id='otable']/tbody/tr[1]/td[1]/a").click()
- # give it 1 sec so the pop-up becomes the "active_element"
- time.sleep(1)
- element = self.driver.switch_to.active_element
- check_list = ['Order', 'Configuration file', 'Operation', 'Line number']
- for item in check_list:
- if item not in element.text:
- self.log.error("%s not found" %item)
- # any better way to close this pop-up? ... TBD
- element.find_element_by_class_name("close").click()
- # step 10 : need to manually check "Yocto Manual" in saved screen
- self.driver.find_element_by_css_selector("i.icon-share.get-info").click()
- # save screen here
- time.sleep(5)
- self.save_screenshot(screenshot_type='native', append_name='step10')
-
-
- ##############
- # CASE 947 #
- ##############
- def test_947(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_link_text("core-image-minimal").click()
- self.find_element_by_link_text_in_table('nav', 'Configuration').click()
- # step 2
- self.driver.find_element_by_link_text("BitBake variables").click()
- # step 3
- def xpath_option(column_name):
- # return xpath of options under "Edit columns" button
- return self.shortest_xpath('id', 'navTab') + self.shortest_xpath('id', 'editcol') \
- + self.shortest_xpath('id', column_name)
- self.driver.find_element_by_id('edit-columns-button').click()
- # by default, option "Description" and "Set in file" were checked
- self.driver.find_element_by_xpath(xpath_option('description')).click()
- self.driver.find_element_by_xpath(xpath_option('file')).click()
- self.driver.find_element_by_id('edit-columns-button').click()
- check_list = ['Description', 'Set in file']
- head_list = self.get_table_head_text('otable')
- for item in check_list:
- self.assertFalse(item in head_list, msg=("item %s should not be in head row" % item))
- # check these 2 options and verify again
- self.driver.find_element_by_id('edit-columns-button').click()
- self.driver.find_element_by_xpath(xpath_option('description')).click()
- self.driver.find_element_by_xpath(xpath_option('file')).click()
- self.driver.find_element_by_id('edit-columns-button').click()
- head_list = self.get_table_head_text('otable')
- for item in check_list:
- self.assertTrue(item in head_list, msg=("item %s not in head row" % item))
-
-
- ##############
- # CASE 948 #
- ##############
- def test_948(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_link_text("core-image-minimal").click()
- self.find_element_by_link_text_in_table('nav', 'Configuration').click()
- self.driver.find_element_by_link_text("BitBake variables").click()
- #get number of variables visible by default
- number_before_search = self.driver.find_element_by_class_name('page-header').text
- # search for a while...
- self.driver.find_element_by_id("search").clear()
- self.driver.find_element_by_id("search").send_keys("BB")
- self.driver.find_element_by_id("search-button").click()
- #get number of variables visible after search
- number_after_search = self.driver.find_element_by_class_name('page-header').text
- self.assertTrue(number_before_search > number_after_search, msg=("items should be less after search"))
-
-
- ##############
- # CASE 949 #
- ##############
- def test_949(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_link_text("core-image-minimal").click()
- self.find_element_by_link_text_in_table('nav', 'core-image-minimal').click()
- # step 3
- try:
- self.driver.find_element_by_partial_link_text("Packages included")
- self.driver.find_element_by_partial_link_text("Directory structure")
- except Exception as e:
- self.log.error(e)
- self.assertFalse(True)
- # step 4
- head_list = self.get_table_head_text('otable')
- for item in ['Package', 'Package version', 'Size', 'Dependencies', 'Reverse dependencies', 'Recipe']:
- self.assertTrue(item in head_list, msg=("item %s not in head row" % item))
- # step 5-6
- self.driver.find_element_by_id("edit-columns-button").click()
- selectable_class = 'checkbox'
- # minimum-table : means unselectable items
- unselectable_class = 'checkbox muted'
- selectable_check_list = ['Dependencies', 'Layer', 'Layer branch', 'Layer commit', \
- 'License', 'Recipe', 'Recipe version', 'Reverse dependencies', \
- 'Size', 'Size over total (%)']
- unselectable_check_list = ['Package', 'Package version']
- selectable_list = list()
- unselectable_list = list()
- selectable_elements = self.driver.find_elements_by_xpath("//*[@id='editcol']//*[@class='" + selectable_class + "']")
- unselectable_elements = self.driver.find_elements_by_xpath("//*[@id='editcol']//*[@class='" + unselectable_class + "']")
- for element in selectable_elements:
- selectable_list.append(element.text)
- for element in unselectable_elements:
- unselectable_list.append(element.text)
- # check them
- for item in selectable_check_list:
- self.assertTrue(item in selectable_list, msg=("%s not found in dropdown menu" % item))
- for item in unselectable_check_list:
- self.assertTrue(item in unselectable_list, msg=("%s not found in dropdown menu" % item))
- self.driver.find_element_by_id("edit-columns-button").click()
- # step 7
- self.driver.find_element_by_partial_link_text("Directory structure").click()
- head_list = self.get_table_head_text('dirtable')
- for item in ['Directory / File', 'Symbolic link to', 'Source package', 'Size', 'Permissions', 'Owner', 'Group']:
- self.assertTrue(item in head_list, msg=("%s not found in Directory structure table head" % item))
-
- ##############
- # CASE 950 #
- ##############
- def test_950(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- # step3&4: so far we're not sure if there's "successful build" or "failed
- # build".If either of them doesn't exist, we can still go on other steps
- check_list = ['Configuration', 'Tasks', 'Recipes', 'Packages', 'Time', 'CPU usage', 'Disk I/O']
- has_successful_build = 1
- has_failed_build = 1
- try:
- pass_icon = self.driver.find_element_by_xpath("//*[@class='icon-ok-sign success']")
- except Exception:
- self.log.info("no successful build exists")
- has_successful_build = 0
- pass
- if has_successful_build:
- pass_icon.click()
- # save screen here to check if it matches requirement.
- self.browser_delay()
- self.save_screenshot(screenshot_type='selenium', append_name='step3_1')
- for item in check_list:
- try:
- self.find_element_by_link_text_in_table('nav', item)
- except Exception:
- self.assertFalse(True, msg=("link %s cannot be found in the page" % item))
- # step 6
- check_list_2 = ['Packages included', 'Total package size', \
- 'License manifest', 'Image files']
- self.assertTrue(self.is_text_present(check_list_2), msg=("text not in web page"))
- self.driver.back()
- try:
- fail_icon = self.driver.find_element_by_xpath("//*[@class='icon-minus-sign error']")
- except Exception:
- has_failed_build = 0
- self.log.info("no failed build exists")
- pass
- if has_failed_build:
- fail_icon.click()
- # save screen here to check if it matches requirement.
- self.browser_delay()
- self.save_screenshot(screenshot_type='selenium', append_name='step3_2')
- for item in check_list:
- try:
- self.find_element_by_link_text_in_table('nav', item)
- except Exception:
- self.assertFalse(True, msg=("link %s cannot be found in the page" % item))
- # step 7 involved
- check_list_3 = ['Machine', 'Distro', 'Layers', 'Total number of tasks', 'Tasks executed', \
- 'Tasks not executed', 'Reuse', 'Recipes built', 'Packages built']
- self.assertTrue(self.is_text_present(check_list_3), msg=("text not in web page"))
- self.driver.back()
-
-
- ##############
- # CASE 951 #
- ##############
- def test_951(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- # currently test case itself isn't responsible for creating "1 successful and
- # 1 failed build"
- has_successful_build = 1
- has_failed_build = 1
- try:
- fail_icon = self.driver.find_element_by_xpath("//*[@class='icon-minus-sign error']")
- except Exception:
- has_failed_build = 0
- self.log.info("no failed build exists")
- pass
- # if there's failed build, we can proceed
- if has_failed_build:
- self.driver.find_element_by_partial_link_text("error").click()
- self.driver.back()
- # not sure if there "must be" some warnings, so here save a screen
- self.browser_delay()
- self.save_screenshot(screenshot_type='selenium', append_name='step4')
-
-
- ##############
- # CASE 955 #
- ##############
- def test_955(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.log.info(" You should manually create all images before test starts!")
- # So far the case itself is not responsable for creating all sorts of images.
- # So assuming they are already there
- # step 2
- self.driver.find_element_by_link_text("core-image-minimal").click()
- # save screen here to see the page component
-
-
- ##############
- # CASE 956 #
- ##############
- def test_956(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- # step 2-3 need to run manually
- self.log.info("step 2-3: checking the help message when you hover on help icon of target,\
- tasks, recipes, packages need to run manually")
- self.driver.find_element_by_partial_link_text("Manual").click()
- if not self.is_text_present("Manual"):
- self.log.error("please check [Toaster manual] link on page")
- self.failIf(True)
-
-####################################################################################################
-# Starting backend tests ###########################################################################
-####################################################################################################
-
- ##############
- # CASE 1066 #
- ##############
- def test_1066(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- con=sqlite.connect('toaster.sqlite')
- cursor = con.cursor()
- query = "select count(name) from orm_project a, auth_user b where a.user_id = b.id and b.username='_anonuser';"
- cursor.execute(query)
- data = cursor.fetchone()
- self.failUnless(data >= 1)
-
-
- ##############
- # CASE 1071 #
- ##############
- def test_1071(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- con=sqlite.connect('toaster.sqlite')
- cursor = con.cursor()
- query = "select name from orm_release;"
- cursor.execute(query)
- data = cursor.fetchall()
- for i in range(0,4):
- data[i] = data[i][0]
- data.sort()
- print(data)
- json_parse = json.loads(open('toasterconf.json').read())
- json_data = []
- for i in range (0,4):
- json_data.append(json_parse['releases'][i]['name'])
- json_data.sort()
- print(json_data)
- self.failUnless(data == json_data)
-
- ##############
- # CASE 1072 #
- ##############
- def test_1072(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- con=sqlite.connect('toaster.sqlite')
- cursor = con.cursor()
- query = "select value from orm_toastersetting where name like 'DEFCONF%';"
- cursor.execute(query)
- data = cursor.fetchall()
- for i in range(0,6):
- data[i] = data[i][0]
- print(data)
- json_parse = json.loads(open('toasterconf.json').read())
- json_data=json_parse['config']
- json_data = json_data.values()
- print(json_data)
- self.failUnless(data == json_data)
-
-
- ##############
- # CASE 1074 #
- ##############
- def test_1074(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- con=sqlite.connect('toaster.sqlite')
- cursor = con.cursor()
- query = "select name from orm_layersource;"
- cursor.execute(query)
- data = cursor.fetchall()
- for i in range(0,3):
- data[i] = data[i][0]
- print(data)
- json_parse = json.loads(open('toasterconf.json').read())
- json_data = []
- for i in range(0,3):
- json_data.append(json_parse['layersources'][i]['name'])
- print(json_data)
- self.failUnless(set(data) == set(json_data))
-
- ##############
- # CASE 1075 #
- ##############
- def test_1075(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- con=sqlite.connect('toaster.sqlite')
- cursor = con.cursor()
- query = "select value from orm_toastersetting where name like 'DEFAULT_RELEASE';"
- cursor.execute(query)
- data = cursor.fetchall()
- data = data[0][0]
- print(data)
- json_parse = json.loads(open('toasterconf.json').read())
- json_data = json_parse['defaultrelease']
- print(json_data)
- self.failUnless(set(data) == set(json_data))
-
- ##############
- # CASE 1076 #
- ##############
- def test_1076(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
-
- print('Checking branches for "Local Yocto Project"')
- con=sqlite.connect('toaster.sqlite')
- cursor = con.cursor()
- query = "select name from orm_branch where layer_source_id=1;"
- cursor.execute(query)
- data = cursor.fetchall()
- lenght = len(data)
- try:
- for i in range(0,lenght):
- data[i] = data[i][0]
- except:
- pass
- print(data)
- json_parse = json.loads(open('toasterconf.json').read())
- json_location = json_parse['layersources'][0]['name']
- print(json_location)
- json_data = json_parse['layersources'][0]['branches']
- print(json_data)
- self.failUnless(set(data) == set(json_data))
-
- print('Checking branches for "OpenEmbedded"')
- con=sqlite.connect('toaster.sqlite')
- cursor = con.cursor()
- query = "select name from orm_branch where layer_source_id=3;"
- cursor.execute(query)
- data = cursor.fetchall()
- lenght = len(data)
- for i in range(0,lenght):
- data[i] = data[i][0]
- print(data)
- json_parse = json.loads(open('toasterconf.json').read())
- json_location = json_parse['layersources'][1]['name']
- print(json_location)
- json_data = json_parse['layersources'][1]['branches']
- print(json_data)
- self.failUnless(set(data) == set(json_data))
-
- print('Checking branches for "Imported layers"')
- con=sqlite.connect('toaster.sqlite')
- cursor = con.cursor()
- query = "select name from orm_branch where layer_source_id=2;"
- cursor.execute(query)
- data = cursor.fetchall()
- lenght = len(data)
- for i in range(0,lenght):
- data[i] = data[i][0]
- print(data)
- json_parse = json.loads(open('toasterconf.json').read())
- json_location = json_parse['layersources'][2]['name']
- print(json_location)
- json_data = json_parse['layersources'][2]['branches']
- print(json_data)
- self.failUnless(set(data) == set(json_data))
-
-
- ##############
- # CASE 1077 #
- ##############
- def test_1077(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- con=sqlite.connect('toaster.sqlite')
- cursor = con.cursor()
- query = "select name from orm_bitbakeversion;"
- cursor.execute(query)
- data = cursor.fetchall()
- for i in range(0,4):
- data[i] = data[i][0]
- print(data)
- json_parse = json.loads(open('toasterconf.json').read())
- json_data = []
- for i in range(0,4):
- json_data.append(json_parse['bitbake'][i]['name'])
- print(json_data)
- self.failUnless(set(data) == set(json_data))
-
- ##############
- # CASE 1083 #
- ##############
- def test_1083(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_id("new-project-button").click()
- self.driver.find_element_by_id("new-project-name").send_keys("new-test-project")
- self.driver.find_element_by_id("create-project-button").click()
- con=sqlite.connect('toaster.sqlite')
- cursor = con.cursor()
- query = "select count(name) from orm_project where name = 'new-test-project';"
- cursor.execute(query)
- data = cursor.fetchone()
- print('data: %s' % data)
- self.failUnless(data >= 1)
-
- ##############
- # CASE 1084 #
- ##############
- def test_1084(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_id("new-project-button").click()
- self.driver.find_element_by_id("new-project-name").send_keys("new-default-project")
- self.driver.find_element_by_id("create-project-button").click()
- con=sqlite.connect('toaster.sqlite')
- cursor = con.cursor()
- query = "select a.name from orm_release a, orm_project b where a.id = b.release_id and b.name = 'new-default-project' limit 1;"
- cursor.execute(query)
- db_data = str(cursor.fetchone()[0])
- json_parse = json.loads(open('toasterconf.json').read())
- json_data = str(json_parse['defaultrelease'])
- self.failUnless(db_data == json_data)
-
- ##############
- # CASE 1088 #
- ##############
- def test_1088(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_css_selector("a[href='/toastergui/projects/']").click()
- self.driver.find_element_by_link_text('new-default-project').click()
- self.driver.find_element_by_id('project-change-form-toggle').click()
- self.driver.find_element_by_id('project-name-change-input').clear()
- self.driver.find_element_by_id('project-name-change-input').send_keys('new-name')
- self.driver.find_element_by_id('project-name-change-btn').click()
- con=sqlite.connect('toaster.sqlite')
- cursor = con.cursor()
- query = "select count(name) from orm_project where name = 'new-name';"
- cursor.execute(query)
- data = cursor.fetchone()[0]
- self.failUnless(data == 1)
- #reseting project name
- self.driver.find_element_by_id('project-change-form-toggle').click()
- self.driver.find_element_by_id('project-name-change-input').clear()
- self.driver.find_element_by_id('project-name-change-input').send_keys('new-default-project')
- self.driver.find_element_by_id('project-name-change-btn').click()
-
-
- ##############
- # CASE 1089 #
- ##############
- def test_1089(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_css_selector("a[href='/toastergui/projects/']").click()
- self.driver.find_element_by_link_text('new-default-project').click()
- self.driver.find_element_by_id('change-machine-toggle').click()
- self.driver.find_element_by_id('machine-change-input').clear()
- self.driver.find_element_by_id('machine-change-input').send_keys('qemuarm64')
-# self.driver.find_element_by_id('machine-change-input').send_keys(Keys.RETURN)
- self.driver.find_element_by_id('machine-change-btn').click()
- con=sqlite.connect('toaster.sqlite')
- cursor = con.cursor()
- query = "select count(id) from orm_projectvariable where name like 'machine' and value like 'qemuarm64';"
- cursor.execute(query)
- data = cursor.fetchone()[0]
- self.failUnless(data == 1)
- #resetting machine to default value
- self.driver.find_element_by_id('change-machine-toggle').click()
- self.driver.find_element_by_id('machine-change-input').clear()
- self.driver.find_element_by_id('machine-change-input').send_keys('qemux86')
- self.driver.find_element_by_id('machine-change-input').send_keys(Keys.RETURN)
- self.driver.find_element_by_id('machine-change-btn').click()
-
- ##############
- # CASE 1090 #
- ##############
- def test_1090(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- con=sqlite.connect('toaster.sqlite')
- cursor = con.cursor()
- query = "select username from auth_user where is_superuser = 1;"
- cursor.execute(query)
- data = cursor.fetchall()
- try:
- data = data[0][0]
- except:
- pass
- print(data)
- self.failUnless(data == 'toaster_admin')
-
- ##############
- # CASE 1091 #
- ##############
- def test_1091(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- self.driver.get(self.base_url)
- self.driver.find_element_by_css_selector("a[href='/toastergui/projects/']").click()
- self.driver.find_element_by_link_text('new-default-project').click()
- self.driver.find_element_by_id('release-change-toggle').click()
- dropdown = self.driver.find_element_by_css_selector('select')
- for option in dropdown.find_elements_by_tag_name('option'):
- if option.text == 'Local Yocto Project':
- option.click()
- self.driver.find_element_by_id('change-release-btn').click()
- #wait for the changes to register in the DB
- time.sleep(1)
- con=sqlite.connect('toaster.sqlite')
- cursor = con.cursor()
- query = "select count(*) from orm_layer_version a, orm_projectlayer b, orm_project c where a.\"commit\"=\"HEAD\" and a.id = b.layercommit_id and b.project_id=c.id and c.name='new-default-project';"
- cursor.execute(query)
- data = cursor.fetchone()[0]
- #resetting release to default
- self.driver.find_element_by_id('release-change-toggle').click()
- dropdown = self.driver.find_element_by_css_selector('select')
- for option in dropdown.find_elements_by_tag_name('option'):
- if option.text == 'Yocto Project master':
- option.click()
- self.driver.find_element_by_id('change-release-btn').click()
- #wait for the changes to register in the DB
- time.sleep(1)
- self.failUnless(data == 3)
-
- ##############
- # CASE 1092 #
- ##############
- def test_1092(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
- self.driver.maximize_window()
- con=sqlite.connect('toaster.sqlite')
- cursor = con.cursor()
- query = "select a.name, a.value from orm_projectvariable a, orm_project b where a.project_id = b.id and b.name = 'new-default-project';"
- cursor.execute(query)
- data = dict(cursor.fetchall())
- print(data)
- default_values = {u'IMAGE_INSTALL_append': u'', u'PACKAGE_CLASSES': u'package_rpm', u'MACHINE': u'qemux86', u'SDKMACHINE': u'x86_64', u'DISTRO': u'poky', u'IMAGE_FSTYPES': u'ext3 jffs2 tar.bz2'}
- self.failUnless(data == default_values)
-
- ##############
- # CASE 1093 #
- ##############
- def test_1093(self):
- self.case_no = self.get_case_number()
- self.log.info(' CASE %s log: ' % str(self.case_no))
-
- #get initial values
- con=sqlite.connect('toaster.sqlite')
- cursor = con.cursor()
- query = "select layercommit_id from orm_projectlayer a, orm_project b where a.project_id=b.id and b.name='new-default-project';"
- cursor.execute(query)
- data_initial = cursor.fetchall()
- print(data_initial)
-
- self.driver.maximize_window()
- self.driver.get('localhost:8000')#self.base_url)
- self.driver.find_element_by_css_selector("a[href='/toastergui/projects/']").click()
- self.driver.find_element_by_link_text('new-default-project').click()
- self.driver.find_element_by_id('release-change-toggle').click()
- dropdown = self.driver.find_element_by_css_selector('select')
- for option in dropdown.find_elements_by_tag_name('option'):
- if option.text == 'Local Yocto Project':
- option.click()
- self.driver.find_element_by_id('change-release-btn').click()
- #wait for the changes to register in the DB
- time.sleep(1)
-
- #get changed values
- con=sqlite.connect('toaster.sqlite')
- cursor = con.cursor()
- query = "select layercommit_id from orm_projectlayer a, orm_project b where a.project_id=b.id and b.name='new-default-project';"
- cursor.execute(query)
- data_changed = cursor.fetchall()
- print(data_changed)
-
- #resetting release to default
- self.driver.find_element_by_id('release-change-toggle').click()
- dropdown = self.driver.find_element_by_css_selector('select')
- for option in dropdown.find_elements_by_tag_name('option'):
- if option.text == 'Yocto Project master':
- option.click()
- self.driver.find_element_by_id('change-release-btn').click()
- #wait for the changes to register in the DB
- time.sleep(1)
- self.failUnless(data_initial != data_changed)
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/toaster_test.cfg b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/toaster_test.cfg
deleted file mode 100644
index 685a9ee6a..000000000
--- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/toasteruitest/toaster_test.cfg
+++ /dev/null
@@ -1,25 +0,0 @@
-# Configuration file for toaster_test
-# Sorted by different host type
-
-# test browser could be: firefox; chrome; ie(still under development)
-# logging_level could be: CRITICAL; ERROR; WARNING; INFO; DEBUG; NOTSET
-
-
-[toaster_test_linux]
-toaster_url = 'http://127.0.0.1:8000'
-test_browser = 'firefox'
-test_cases = [946]
-logging_level = 'INFO'
-
-
-[toaster_test_windows]
-toaster_url = 'http://127.0.0.1:8000'
-test_browser = ['ie', 'firefox', 'chrome']
-test_cases = [901, 902, 903]
-logging_level = 'DEBUG'
-
-[toaster_test_darwin]
-toaster_url = 'http://127.0.0.1:8000'
-test_browser = 'firefox'
-test_cases = [901, 902, 903, 904, 906, 910, 911, 912, 913, 914, 915, 916, 923, 924, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 955, 956]
-logging_level = 'INFO'
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/urlcheck.py b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/urlcheck.py
deleted file mode 100644
index 001fcee96..000000000
--- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/urlcheck.py
+++ /dev/null
@@ -1,53 +0,0 @@
-from __future__ import print_function
-import sys
-
-import httplib2
-import config
-import urllist
-
-
-config.logger.info("Testing %s with %s", config.TOASTER_BASEURL, config.W3C_VALIDATOR)
-
-def validate_html5(url):
- http_client = httplib2.Http(None)
- status = "Failed"
- errors = -1
- warnings = -1
-
- urlrequest = config.W3C_VALIDATOR+url
-
- # pylint: disable=broad-except
- # we disable the broad-except because we want to actually catch all possible exceptions
- try:
- resp, _ = http_client.request(urlrequest, "HEAD")
- if resp['x-w3c-validator-status'] != "Abort":
- status = resp['x-w3c-validator-status']
- errors = int(resp['x-w3c-validator-errors'])
- warnings = int(resp['x-w3c-validator-warnings'])
-
- if status == 'Invalid':
- config.logger.warning("Failed %s is %s\terrors %s warnings %s (check at %s)", url, status, errors, warnings, urlrequest)
- else:
- config.logger.debug("OK! %s", url)
-
- except Exception as exc:
- config.logger.warning("Failed validation call: %s", exc)
- return (status, errors, warnings)
-
-
-def print_validation(url):
- status, errors, warnings = validate_html5(url)
- config.logger.error("url %s is %s\terrors %s warnings %s (check at %s)", url, status, errors, warnings, config.W3C_VALIDATOR+url)
-
-
-def main():
- print("Testing %s with %s" % (config.TOASTER_BASEURL, config.W3C_VALIDATOR))
-
- if len(sys.argv) > 1:
- print_validation(sys.argv[1])
- else:
- for url in urllist.URLS:
- print_validation(config.TOASTER_BASEURL+url)
-
-if __name__ == "__main__":
- main()
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/urllist.py b/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/urllist.py
deleted file mode 100644
index 6db9ffc7b..000000000
--- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/urllist.py
+++ /dev/null
@@ -1,39 +0,0 @@
-URLS = [
- 'toastergui/landing/',
- 'toastergui/builds/',
- 'toastergui/build/1',
- 'toastergui/build/1/tasks/',
- 'toastergui/build/1/tasks/1/',
- 'toastergui/build/1/task/1',
- 'toastergui/build/1/recipes/',
- 'toastergui/build/1/recipe/1/active_tab/1',
- 'toastergui/build/1/recipe/1',
- 'toastergui/build/1/recipe_packages/1',
- 'toastergui/build/1/packages/',
- 'toastergui/build/1/package/1',
- 'toastergui/build/1/package_built_dependencies/1',
- 'toastergui/build/1/package_included_detail/1/1',
- 'toastergui/build/1/package_included_dependencies/1/1',
- 'toastergui/build/1/package_included_reverse_dependencies/1/1',
- 'toastergui/build/1/target/1',
- 'toastergui/build/1/target/1/targetpkg',
- 'toastergui/build/1/target/1/dirinfo',
- 'toastergui/build/1/target/1/dirinfo_filepath/_/bin/bash',
- 'toastergui/build/1/configuration',
- 'toastergui/build/1/configvars',
- 'toastergui/build/1/buildtime',
- 'toastergui/build/1/cpuusage',
- 'toastergui/build/1/diskio',
- 'toastergui/build/1/target/1/packagefile/1',
- 'toastergui/newproject/',
- 'toastergui/projects/',
- 'toastergui/project/1',
- 'toastergui/project/1/configuration',
- 'toastergui/project/1/builds/',
- 'toastergui/project/1/layers/',
- 'toastergui/project/1/layer/1',
- 'toastergui/project/1/importlayer',
- 'toastergui/project/1/targets/',
- 'toastergui/project/1/machines/',
- 'toastergui/',
-]
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/oe-core.xml b/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/oe-core.xml
index a6c834f44..66c3595f8 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/oe-core.xml
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/oe-core.xml
@@ -8,21 +8,28 @@
<!-- Bitbake versions which correspond to the metadata release -->
<object model="orm.bitbakeversion" pk="1">
- <field type="CharField" name="name">morty</field>
+ <field type="CharField" name="name">pyro</field>
<field type="CharField" name="giturl">git://git.openembedded.org/bitbake</field>
- <field type="CharField" name="branch">1.32</field>
+ <field type="CharField" name="branch">1.34</field>
</object>
<object model="orm.bitbakeversion" pk="2">
<field type="CharField" name="name">HEAD</field>
+ <field type="CharField" name="giturl">git://git.openembedded.org/bitbake</field>
+ <field type="CharField" name="branch">HEAD</field>
+ </object>
+ <object model="orm.bitbakeversion" pk="3">
+ <field type="CharField" name="name">master</field>
+ <field type="CharField" name="giturl">git://git.openembedded.org/bitbake</field>
+ <field type="CharField" name="branch">master</field>
</object>
<!-- Releases available -->
<object model="orm.release" pk="1">
- <field type="CharField" name="name">morty</field>
- <field type="CharField" name="description">Openembedded Morty</field>
+ <field type="CharField" name="name">pyro</field>
+ <field type="CharField" name="description">Openembedded Pyro</field>
<field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">1</field>
- <field type="CharField" name="branch_name">morty</field>
- <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href=\"http://cgit.openembedded.org/openembedded-core/log/?h=morty\"&gt;OpenEmbedded Morty&lt;/a&gt; branch.</field>
+ <field type="CharField" name="branch_name">pyro</field>
+ <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href=\"http://cgit.openembedded.org/openembedded-core/log/?h=pyro\"&gt;OpenEmbedded Pyro&lt;/a&gt; branch.</field>
</object>
<object model="orm.release" pk="2">
<field type="CharField" name="name">local</field>
@@ -31,21 +38,36 @@
<field type="CharField" name="branch_name">HEAD</field>
<field type="TextField" name="helptext">Toaster will run your builds with the version of OpenEmbedded that you have cloned or downloaded to your computer.</field>
</object>
+ <object model="orm.release" pk="3">
+ <field type="CharField" name="name">master</field>
+ <field type="CharField" name="description">OpenEmbedded core master</field>
+ <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">3</field>
+ <field type="CharField" name="branch_name">master</field>
+ <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href=\"http://cgit.openembedded.org/openembedded-core/log/\"&gt;OpenEmbedded master&lt;/a&gt; branch.</field>
+ </object>
<!-- Default layers for each release -->
<object model="orm.releasedefaultlayer" pk="1">
<field rel="ManyToOneRel" to="orm.release" name="release">1</field>
<field type="CharField" name="layer_name">openembedded-core</field>
</object>
- <object model="orm.releasedefaultlayer" pk="4">
+ <object model="orm.releasedefaultlayer" pk="2">
<field rel="ManyToOneRel" to="orm.release" name="release">2</field>
<field type="CharField" name="layer_name">openembedded-core</field>
</object>
+ <object model="orm.releasedefaultlayer" pk="3">
+ <field rel="ManyToOneRel" to="orm.release" name="release">3</field>
+ <field type="CharField" name="layer_name">openembedded-core</field>
+ </object>
+
- <!-- TYPE_LOCAL = 0 Layers for the Local release -->
+ <!-- Layer for the Local release -->
<object model="orm.layer" pk="1">
<field type="CharField" name="name">openembedded-core</field>
<field type="CharField" name="vcs_url">git://git.openembedded.org/openembedded-core</field>
+ <field type="CharField" name="vcs_web_url">http://cgit.openembedded.org/openembedded-core</field>
+ <field type="CharField" name="vcs_web_tree_base_url">http://cgit.openembedded.org/openembedded-core/tree/%path%?h=%branch%</field>
+ <field type="CharField" name="vcs_web_file_base_url">http://cgit.openembedded.org/openembedded-core/tree/%path%?h=%branch%</field>
</object>
<object model="orm.layer_version" pk="1">
<field rel="ManyToOneRel" to="orm.layer" name="layer">1</field>
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/poky.xml b/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/poky.xml
index c192baa42..7827aac28 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/poky.xml
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/poky.xml
@@ -8,9 +8,9 @@
<!-- Bitbake versions which correspond to the metadata release -->
<object model="orm.bitbakeversion" pk="1">
- <field type="CharField" name="name">morty</field>
+ <field type="CharField" name="name">pyro</field>
<field type="CharField" name="giturl">git://git.yoctoproject.org/poky</field>
- <field type="CharField" name="branch">morty</field>
+ <field type="CharField" name="branch">pyro</field>
<field type="CharField" name="dirpath">bitbake</field>
</object>
<object model="orm.bitbakeversion" pk="2">
@@ -19,14 +19,21 @@
<field type="CharField" name="branch">HEAD</field>
<field type="CharField" name="dirpath">bitbake</field>
</object>
+ <object model="orm.bitbakeversion" pk="3">
+ <field type="CharField" name="name">master</field>
+ <field type="CharField" name="giturl">git://git.yoctoproject.org/poky</field>
+ <field type="CharField" name="branch">master</field>
+ <field type="CharField" name="dirpath">bitbake</field>
+ </object>
+
<!-- Releases available -->
<object model="orm.release" pk="1">
- <field type="CharField" name="name">morty</field>
- <field type="CharField" name="description">Yocto Project 2.2 "Morty"</field>
+ <field type="CharField" name="name">pyro</field>
+ <field type="CharField" name="description">Yocto Project 2.3 "Pyro"</field>
<field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">1</field>
- <field type="CharField" name="branch_name">morty</field>
- <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href="http://git.yoctoproject.org/cgit/cgit.cgi/poky/log/?h=morty"&gt;Yocto Project Morty branch&lt;/a&gt;.</field>
+ <field type="CharField" name="branch_name">pyro</field>
+ <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href="http://git.yoctoproject.org/cgit/cgit.cgi/poky/log/?h=pyro"&gt;Yocto Project Pyro branch&lt;/a&gt;.</field>
</object>
<object model="orm.release" pk="2">
<field type="CharField" name="name">local</field>
@@ -35,8 +42,15 @@
<field type="CharField" name="branch_name">HEAD</field>
<field type="TextField" name="helptext">Toaster will run your builds with the version of the Yocto Project you have cloned or downloaded to your computer.</field>
</object>
+ <object model="orm.release" pk="3">
+ <field type="CharField" name="name">master</field>
+ <field type="CharField" name="description">Yocto Project master</field>
+ <field rel="ManyToOneRel" to="orm.bitbakeversion" name="bitbake_version">3</field>
+ <field type="CharField" name="branch_name">master</field>
+ <field type="TextField" name="helptext">Toaster will run your builds using the tip of the &lt;a href="http://git.yoctoproject.org/cgit/cgit.cgi/poky/log/"&gt;Yocto Project Master branch&lt;/a&gt;.</field>
+ </object>
- <!-- Default layers for each release -->
+ <!-- Default project layers for each release -->
<object model="orm.releasedefaultlayer" pk="1">
<field rel="ManyToOneRel" to="orm.release" name="release">1</field>
<field type="CharField" name="layer_name">openembedded-core</field>
@@ -61,31 +75,71 @@
<field rel="ManyToOneRel" to="orm.release" name="release">2</field>
<field type="CharField" name="layer_name">meta-yocto-bsp</field>
</object>
+ <object model="orm.releasedefaultlayer" pk="7">
+ <field rel="ManyToOneRel" to="orm.release" name="release">3</field>
+ <field type="CharField" name="layer_name">openembedded-core</field>
+ </object>
+ <object model="orm.releasedefaultlayer" pk="8">
+ <field rel="ManyToOneRel" to="orm.release" name="release">3</field>
+ <field type="CharField" name="layer_name">meta-poky</field>
+ </object>
+ <object model="orm.releasedefaultlayer" pk="9">
+ <field rel="ManyToOneRel" to="orm.release" name="release">3</field>
+ <field type="CharField" name="layer_name">meta-yocto-bsp</field>
+ </object>
- <!-- Layers for the Local release
- layersource TYPE_LOCAL = 0
+ <!-- Default layers provided by poky
+ openembedded-core
+ meta-poky
+ meta-yocto-bsp
-->
<object model="orm.layer" pk="1">
<field type="CharField" name="name">openembedded-core</field>
<field type="CharField" name="layer_index_url"></field>
<field type="CharField" name="vcs_url">git://git.yoctoproject.org/poky</field>
+ <field type="CharField" name="vcs_web_url">http://git.yoctoproject.org/cgit/cgit.cgi/poky</field>
+ <field type="CharField" name="vcs_web_tree_base_url">http://git.yoctoproject.org/cgit/cgit.cgi/poky/tree/%path%?h=%branch%</field>
+ <field type="CharField" name="vcs_web_file_base_url">http://git.yoctoproject.org/cgit/cgit.cgi/poky/tree/%path%?h=%branch%</field>
</object>
<object model="orm.layer_version" pk="1">
<field rel="ManyToOneRel" to="orm.layer" name="layer">1</field>
<field type="IntegerField" name="layer_source">0</field>
+ <field rel="ManyToOneRel" to="orm.release" name="release">1</field>
+ <field type="CharField" name="branch">pyro</field>
+ <field type="CharField" name="dirpath">meta</field>
+ </object>
+ <object model="orm.layer_version" pk="2">
+ <field rel="ManyToOneRel" to="orm.layer" name="layer">1</field>
+ <field type="IntegerField" name="layer_source">0</field>
<field rel="ManyToOneRel" to="orm.release" name="release">2</field>
<field type="CharField" name="branch">HEAD</field>
<field type="CharField" name="commit">HEAD</field>
<field type="CharField" name="dirpath">meta</field>
</object>
-
+ <object model="orm.layer_version" pk="3">
+ <field rel="ManyToOneRel" to="orm.layer" name="layer">1</field>
+ <field type="IntegerField" name="layer_source">0</field>
+ <field rel="ManyToOneRel" to="orm.release" name="release">3</field>
+ <field type="CharField" name="branch">master</field>
+ <field type="CharField" name="dirpath">meta</field>
+ </object>
<object model="orm.layer" pk="2">
<field type="CharField" name="name">meta-poky</field>
<field type="CharField" name="layer_index_url"></field>
<field type="CharField" name="vcs_url">git://git.yoctoproject.org/poky</field>
+ <field type="CharField" name="vcs_web_url">http://git.yoctoproject.org/cgit/cgit.cgi/poky</field>
+ <field type="CharField" name="vcs_web_tree_base_url">http://git.yoctoproject.org/cgit/cgit.cgi/poky/tree/%path%?h=%branch%</field>
+ <field type="CharField" name="vcs_web_file_base_url">http://git.yoctoproject.org/cgit/cgit.cgi/poky/tree/%path%?h=%branch%</field>
</object>
- <object model="orm.layer_version" pk="2">
+ <object model="orm.layer_version" pk="4">
+ <field rel="ManyToOneRel" to="orm.layer" name="layer">2</field>
+ <field type="IntegerField" name="layer_source">0</field>
+ <field rel="ManyToOneRel" to="orm.release" name="release">1</field>
+ <field type="CharField" name="branch">pyro</field>
+ <field type="CharField" name="dirpath">meta-poky</field>
+ </object>
+ <object model="orm.layer_version" pk="5">
<field rel="ManyToOneRel" to="orm.layer" name="layer">2</field>
<field type="IntegerField" name="layer_source">0</field>
<field rel="ManyToOneRel" to="orm.release" name="release">2</field>
@@ -93,14 +147,30 @@
<field type="CharField" name="commit">HEAD</field>
<field type="CharField" name="dirpath">meta-poky</field>
</object>
-
+ <object model="orm.layer_version" pk="6">
+ <field rel="ManyToOneRel" to="orm.layer" name="layer">2</field>
+ <field type="IntegerField" name="layer_source">0</field>
+ <field rel="ManyToOneRel" to="orm.release" name="release">3</field>
+ <field type="CharField" name="branch">master</field>
+ <field type="CharField" name="dirpath">meta-poky</field>
+ </object>
<object model="orm.layer" pk="3">
<field type="CharField" name="name">meta-yocto-bsp</field>
<field type="CharField" name="layer_index_url"></field>
<field type="CharField" name="vcs_url">git://git.yoctoproject.org/poky</field>
+ <field type="CharField" name="vcs_web_url">http://git.yoctoproject.org/cgit/cgit.cgi/poky</field>
+ <field type="CharField" name="vcs_web_tree_base_url">http://git.yoctoproject.org/cgit/cgit.cgi/poky/tree/%path%?h=%branch%</field>
+ <field type="CharField" name="vcs_web_file_base_url">http://git.yoctoproject.org/cgit/cgit.cgi/poky/tree/%path%?h=%branch%</field>
</object>
- <object model="orm.layer_version" pk="3">
+ <object model="orm.layer_version" pk="7">
+ <field rel="ManyToOneRel" to="orm.layer" name="layer">3</field>
+ <field type="IntegerField" name="layer_source">0</field>
+ <field rel="ManyToOneRel" to="orm.release" name="release">1</field>
+ <field type="CharField" name="branch">pyro</field>
+ <field type="CharField" name="dirpath">meta-yocto-bsp</field>
+ </object>
+ <object model="orm.layer_version" pk="8">
<field rel="ManyToOneRel" to="orm.layer" name="layer">3</field>
<field type="IntegerField" name="layer_source">0</field>
<field rel="ManyToOneRel" to="orm.release" name="release">2</field>
@@ -108,4 +178,11 @@
<field type="CharField" name="commit">HEAD</field>
<field type="CharField" name="dirpath">meta-yocto-bsp</field>
</object>
+ <object model="orm.layer_version" pk="9">
+ <field rel="ManyToOneRel" to="orm.layer" name="layer">3</field>
+ <field type="IntegerField" name="layer_source">0</field>
+ <field rel="ManyToOneRel" to="orm.release" name="release">3</field>
+ <field type="CharField" name="branch">master</field>
+ <field type="CharField" name="dirpath">meta-yocto-bsp</field>
+ </object>
</django-objects>
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/settings.xml b/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/settings.xml
index ee6a20285..78c0fdca7 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/settings.xml
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/fixtures/settings.xml
@@ -4,7 +4,7 @@
<!-- pk=1 is DISTRO -->
<object model="orm.toastersetting" pk="2">
<field type="CharField" name="name">DEFAULT_RELEASE</field>
- <field type="CharField" name="value">morty</field>
+ <field type="CharField" name="value">master</field>
</object>
<object model="orm.toastersetting" pk="3">
<field type="CharField" name="name">DEFCONF_PACKAGE_CLASSES</field>
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/management/commands/lsupdates.py b/import-layers/yocto-poky/bitbake/lib/toaster/orm/management/commands/lsupdates.py
index 8ff120e0b..482908d48 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/orm/management/commands/lsupdates.py
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/management/commands/lsupdates.py
@@ -90,7 +90,6 @@ class Command(NoArgsCommand):
from urlparse import urlparse
proxy_settings = os.environ.get("http_proxy", None)
- oe_core_layer = 'openembedded-core'
def _get_json_response(apiurl=DEFAULT_LAYERINDEX_SERVER):
http_progress = Spinner()
@@ -154,41 +153,19 @@ class Command(NoArgsCommand):
total = len(layers_info)
for i, li in enumerate(layers_info):
- # Special case for the openembedded-core layer
- if li['name'] == oe_core_layer:
- try:
- # If we have an existing openembedded-core for example
- # from the toasterconf.json augment the info using the
- # layerindex rather than duplicate it
- oe_core_l = Layer.objects.get(name=oe_core_layer)
- # Take ownership of the layer as now coming from the
- # layerindex
- oe_core_l.summary = li['summary']
- oe_core_l.description = li['description']
- oe_core_l.vcs_web_url = li['vcs_web_url']
- oe_core_l.vcs_web_tree_base_url = \
- li['vcs_web_tree_base_url']
- oe_core_l.vcs_web_file_base_url = \
- li['vcs_web_file_base_url']
-
- oe_core_l.save()
- li_layer_id_to_toaster_layer_id[li['id']] = oe_core_l.pk
- self.mini_progress("layers", i, total)
- continue
-
- except Layer.DoesNotExist:
- pass
-
try:
- l, created = Layer.objects.get_or_create(name=li['name'],
- vcs_url=li['vcs_url'])
+ l, created = Layer.objects.get_or_create(name=li['name'])
l.up_date = li['updated']
- l.vcs_url = li['vcs_url']
- l.vcs_web_url = li['vcs_web_url']
- l.vcs_web_tree_base_url = li['vcs_web_tree_base_url']
- l.vcs_web_file_base_url = li['vcs_web_file_base_url']
l.summary = li['summary']
l.description = li['description']
+
+ if created:
+ # predefined layers in the fixtures (for example poky.xml)
+ # always preempt the Layer Index for these values
+ l.vcs_url = li['vcs_url']
+ l.vcs_web_url = li['vcs_web_url']
+ l.vcs_web_tree_base_url = li['vcs_web_tree_base_url']
+ l.vcs_web_file_base_url = li['vcs_web_file_base_url']
l.save()
except Layer.MultipleObjectsReturned:
logger.info("Skipped %s as we found multiple layers and "
@@ -211,12 +188,14 @@ class Command(NoArgsCommand):
total = len(layerbranches_info)
for i, lbi in enumerate(layerbranches_info):
+ # release as defined by toaster map to layerindex branch
+ release = li_branch_id_to_toaster_release[lbi['branch']]
try:
lv, created = Layer_Version.objects.get_or_create(
- layer_source=LayerSource.TYPE_LAYERINDEX,
layer=Layer.objects.get(
- pk=li_layer_id_to_toaster_layer_id[lbi['layer']])
+ pk=li_layer_id_to_toaster_layer_id[lbi['layer']]),
+ release=release
)
except KeyError:
logger.warning(
@@ -224,11 +203,12 @@ class Command(NoArgsCommand):
lbi['layer'])
continue
- lv.release = li_branch_id_to_toaster_release[lbi['branch']]
- lv.up_date = lbi['updated']
- lv.commit = lbi['actual_branch']
- lv.dirpath = lbi['vcs_subdir']
- lv.save()
+ if created:
+ lv.release = li_branch_id_to_toaster_release[lbi['branch']]
+ lv.up_date = lbi['updated']
+ lv.commit = lbi['actual_branch']
+ lv.dirpath = lbi['vcs_subdir']
+ lv.save()
li_layer_branch_id_to_toaster_lv_id[lbi['id']] =\
lv.pk
@@ -255,9 +235,8 @@ class Command(NoArgsCommand):
layer_id = li_layer_id_to_toaster_layer_id[ldi['dependency']]
dependlist[lv].append(
- Layer_Version.objects.get(
- layer_source=LayerSource.TYPE_LAYERINDEX,
- layer__pk=layer_id))
+ Layer_Version.objects.get(layer__pk=layer_id,
+ release=lv.release))
except Layer_Version.DoesNotExist:
logger.warning("Cannot find layer version (ls:%s),"
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/orm/models.py b/import-layers/yocto-poky/bitbake/lib/toaster/orm/models.py
index a7de57c25..a49f9a432 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/orm/models.py
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/orm/models.py
@@ -38,6 +38,7 @@ import re
import itertools
from signal import SIGUSR1
+
import logging
logger = logging.getLogger("toaster")
@@ -178,24 +179,27 @@ class ProjectManager(models.Manager):
else:
return projects[0]
+
class Project(models.Model):
- search_allowed_fields = ['name', 'short_description', 'release__name', 'release__branch_name']
+ search_allowed_fields = ['name', 'short_description', 'release__name',
+ 'release__branch_name']
name = models.CharField(max_length=100)
short_description = models.CharField(max_length=50, blank=True)
bitbake_version = models.ForeignKey('BitbakeVersion', null=True)
- release = models.ForeignKey("Release", null=True)
- created = models.DateTimeField(auto_now_add = True)
- updated = models.DateTimeField(auto_now = True)
+ release = models.ForeignKey("Release", null=True)
+ created = models.DateTimeField(auto_now_add=True)
+ updated = models.DateTimeField(auto_now=True)
# This is a horrible hack; since Toaster has no "User" model available when
# running in interactive mode, we can't reference the field here directly
- # Instead, we keep a possible null reference to the User id, as not to force
+ # Instead, we keep a possible null reference to the User id,
+ # as not to force
# hard links to possibly missing models
- user_id = models.IntegerField(null = True)
- objects = ProjectManager()
+ user_id = models.IntegerField(null=True)
+ objects = ProjectManager()
# set to True for the project which is the default container
# for builds initiated by the command line etc.
- is_default = models.BooleanField(default = False)
+ is_default= models.BooleanField(default=False)
def __unicode__(self):
return "%s (Release %s, BBV %s)" % (self.name, self.release, self.bitbake_version)
@@ -221,16 +225,16 @@ class Project(models.Model):
return( -1 )
def get_last_outcome(self):
- build_id = self.get_last_build_id
+ build_id = self.get_last_build_id()
if (-1 == build_id):
return( "" )
try:
- return Build.objects.filter( id = self.get_last_build_id )[ 0 ].outcome
+ return Build.objects.filter( id = build_id )[ 0 ].outcome
except (Build.DoesNotExist,IndexError):
return( "not_found" )
def get_last_target(self):
- build_id = self.get_last_build_id
+ build_id = self.get_last_build_id()
if (-1 == build_id):
return( "" )
try:
@@ -239,7 +243,7 @@ class Project(models.Model):
return( "not_found" )
def get_last_errors(self):
- build_id = self.get_last_build_id
+ build_id = self.get_last_build_id()
if (-1 == build_id):
return( 0 )
try:
@@ -248,7 +252,7 @@ class Project(models.Model):
return( "not_found" )
def get_last_warnings(self):
- build_id = self.get_last_build_id
+ build_id = self.get_last_build_id()
if (-1 == build_id):
return( 0 )
try:
@@ -265,7 +269,7 @@ class Project(models.Model):
return last_build.get_image_file_extensions()
def get_last_imgfiles(self):
- build_id = self.get_last_build_id
+ build_id = self.get_last_build_id()
if (-1 == build_id):
return( "" )
try:
@@ -333,20 +337,45 @@ class Project(models.Model):
return queryset
-
def schedule_build(self):
- from bldcontrol.models import BuildRequest, BRTarget, BRLayer, BRVariable, BRBitbake
- br = BuildRequest.objects.create(project = self)
+
+ from bldcontrol.models import BuildRequest, BRTarget, BRLayer
+ from bldcontrol.models import BRBitbake, BRVariable
+
try:
+ now = timezone.now()
+ build = Build.objects.create(project=self,
+ completed_on=now,
+ started_on=now)
+
+ br = BuildRequest.objects.create(project=self,
+ state=BuildRequest.REQ_QUEUED,
+ build=build)
+ BRBitbake.objects.create(req=br,
+ giturl=self.bitbake_version.giturl,
+ commit=self.bitbake_version.branch,
+ dirpath=self.bitbake_version.dirpath)
- BRBitbake.objects.create(req = br,
- giturl = self.bitbake_version.giturl,
- commit = self.bitbake_version.branch,
- dirpath = self.bitbake_version.dirpath)
+ for t in self.projecttarget_set.all():
+ BRTarget.objects.create(req=br, target=t.target, task=t.task)
+ Target.objects.create(build=br.build, target=t.target,
+ task=t.task)
+ # If we're about to build a custom image recipe make sure
+ # that layer is currently in the project before we create the
+ # BRLayer objects
+ customrecipe = CustomImageRecipe.objects.filter(
+ name=t.target,
+ project=self).first()
+ if customrecipe:
+ ProjectLayer.objects.get_or_create(
+ project=self,
+ layercommit=customrecipe.layer_version,
+ optional=False)
for l in self.projectlayer_set.all().order_by("pk"):
commit = l.layercommit.get_vcs_reference()
- print("ii Building layer ", l.layercommit.layer.name, " at vcs point ", commit)
+ logger.debug("Adding layer to build %s" %
+ l.layercommit.layer.name)
BRLayer.objects.create(
req=br,
name=l.layercommit.layer.name,
@@ -357,25 +386,16 @@ class Project(models.Model):
local_source_dir=l.layercommit.layer.local_source_dir
)
- br.state = BuildRequest.REQ_QUEUED
- now = timezone.now()
- br.build = Build.objects.create(project = self,
- completed_on=now,
- started_on=now,
- )
- for t in self.projecttarget_set.all():
- BRTarget.objects.create(req = br, target = t.target, task = t.task)
- Target.objects.create(build = br.build, target = t.target, task = t.task)
-
for v in self.projectvariable_set.all():
- BRVariable.objects.create(req = br, name = v.name, value = v.value)
-
+ BRVariable.objects.create(req=br, name=v.name, value=v.value)
try:
- br.build.machine = self.projectvariable_set.get(name = 'MACHINE').value
+ br.build.machine = self.projectvariable_set.get(
+ name='MACHINE').value
br.build.save()
except ProjectVariable.DoesNotExist:
pass
+
br.save()
signal_runbuilds()
@@ -882,7 +902,7 @@ class Target_Image_File(models.Model):
'ext4.gz', 'ext3', 'ext3.gz', 'hdddirect', 'hddimg', 'iso', 'jffs2',
'jffs2.sum', 'multiubi', 'qcow2', 'squashfs', 'squashfs-lzo',
'squashfs-xz', 'tar', 'tar.bz2', 'tar.gz', 'tar.lz4', 'tar.xz', 'ubi',
- 'ubifs', 'vdi', 'vmdk', 'wic', 'wic.bz2', 'wic.gz', 'wic.lzma'
+ 'ubifs', 'vdi', 'vmdk', 'wic', 'wic.bmap', 'wic.bz2', 'wic.gz', 'wic.lzma'
}
target = models.ForeignKey(Target)
@@ -1365,7 +1385,7 @@ class Layer(models.Model):
name = models.CharField(max_length=100)
layer_index_url = models.URLField()
vcs_url = GitURLField(default=None, null=True)
- local_source_dir = models.TextField(null = True, default = None)
+ local_source_dir = models.TextField(null=True, default=None)
vcs_web_url = models.URLField(null=True, default=None)
vcs_web_tree_base_url = models.URLField(null=True, default=None)
vcs_web_file_base_url = models.URLField(null=True, default=None)
@@ -1473,22 +1493,33 @@ class Layer_Version(models.Model):
return self.commit
return 'N/A'
- def get_detailspage_url(self, project_id):
+ def get_detailspage_url(self, project_id=None):
+ """ returns the url to the layer details page uses own project
+ field if project_id is not specified """
+
+ if project_id is None:
+ project_id = self.project.pk
+
return reverse('layerdetails', args=(project_id, self.pk))
def get_alldeps(self, project_id):
"""Get full list of unique layer dependencies."""
- def gen_layerdeps(lver, project):
+ def gen_layerdeps(lver, project, depth):
+ if depth == 0:
+ return
for ldep in lver.dependencies.all():
yield ldep.depends_on
# get next level of deps recursively calling gen_layerdeps
- for subdep in gen_layerdeps(ldep.depends_on, project):
+ for subdep in gen_layerdeps(ldep.depends_on, project, depth-1):
yield subdep
project = Project.objects.get(pk=project_id)
result = []
- projectlvers = [player.layercommit for player in project.projectlayer_set.all()]
- for dep in gen_layerdeps(self, project):
+ projectlvers = [player.layercommit for player in
+ project.projectlayer_set.all()]
+ # protect against infinite layer dependency loops
+ maxdepth = 20
+ for dep in gen_layerdeps(self, project, maxdepth):
# filter out duplicates and layers already belonging to the project
if dep not in result + projectlvers:
result.append(dep)
@@ -1631,7 +1662,8 @@ class CustomImageRecipe(Recipe):
if base_recipe_path:
base_recipe = open(base_recipe_path, 'r').read()
else:
- raise IOError("Based on recipe file not found")
+ raise IOError("Based on recipe file not found: %s" %
+ base_recipe_path)
# Add a special case for when the recipe we have based a custom image
# recipe on requires another recipe.
@@ -1741,8 +1773,12 @@ def invalidate_cache(**kwargs):
def signal_runbuilds():
"""Send SIGUSR1 to runbuilds process"""
- with open(os.path.join(os.getenv('BUILDDIR'), '.runbuilds.pid')) as pidf:
- os.kill(int(pidf.read()), SIGUSR1)
+ try:
+ with open(os.path.join(os.getenv('BUILDDIR', '.'),
+ '.runbuilds.pid')) as pidf:
+ os.kill(int(pidf.read()), SIGUSR1)
+ except FileNotFoundError:
+ logger.info("Stopping existing runbuilds: no current process found")
django.db.models.signals.post_save.connect(invalidate_cache)
django.db.models.signals.post_delete.connect(invalidate_cache)
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/README b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/README
index 6b09d20d8..352c4fe3e 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/README
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/README
@@ -2,9 +2,9 @@
These tests require Selenium to be installed in your Python environment.
-The simplest way to install this is via pip:
+The simplest way to install this is via pip3:
- pip install selenium==2.53.2
+ pip3 install selenium==2.53.2
Note that if you use other versions of Selenium, some of the tests (such as
tests.browser.test_js_unit_tests.TestJsUnitTests) may fail, as these rely on
@@ -18,7 +18,7 @@ To run tests against Chrome:
* On Windows, put chromedriver.exe in the same directory as chrome.exe
To run tests against PhantomJS (headless):
-
+--NOTE - Selenium seems to be deprecating support for this mode ---
* Download and install PhantomJS:
http://phantomjs.org/download.html
* On *nix systems, put phantomjs on PATH
@@ -43,13 +43,30 @@ Marionette driver.)
The test cases will instantiate a Selenium driver set by the
TOASTER_TESTS_BROWSER environment variable, or Chrome if this is not specified.
+To run tests against the Selenium Firefox Docker container:
+More explanation is located at https://wiki.yoctoproject.org/wiki/TipsAndTricks/TestingToasterWithContainers
+* Run the Selenium container:
+ ** docker run -it --rm=true -p 5900:5900 -p 4444:4444 --name=selenium selenium/standalone-firefox-debug:2.53.0
+ *** 5900 is the default vnc port. If you are runing a vnc server on your machine map a different port e.g. -p 6900:5900 and connect vnc client to 127.0.0.1:6900
+ *** 4444 is the default selenium sever port.
+* Run the tests
+ ** TOASTER_TESTS_BROWSER=http://127.0.0.1:4444/wd/hub TOASTER_TESTS_URL=http://172.17.0.1:8000 ./bitbake/lib/toaster/manage.py test --liveserver=172.17.0.1:8000 tests.browser
+ ** TOASTER_TESTS_BROWSER=remote TOASTER_REMOTE_HUB=http://127.0.0.1:4444/wd/hub ./bitbake/lib/toaster/manage.py test --liveserver=172.17.0.1:8000 tests.browser
+ *** TOASTER_REMOTE_HUB - This is the address for the Selenium Remote Web Driver hub. Assuming you ran the contianer with -p 4444:4444 it will be http://127.0.0.1:4444/wd/hub.
+ *** --liveserver=xxx tells Django to run the test server on an interface and port reachable by both host and container.
+ **** 172.17.0.1 is the default docker bridge on linux, viewable from inside and outside the contianers. Find it with "ip -4 addr show dev docker0"
+* connect to the vnc server to see the tests if you would like
+ ** xtightvncviewer 127.0.0.1:5900
+ ** note, you need to wait for the test container to come up before this can connect.
+
Available drivers:
* chrome (default)
* firefox
* marionette (for newer Firefoxes)
* ie
-* phantomjs
+* phantomjs (deprecated)
+* remote
e.g. to run the test suite with phantomjs where you have phantomjs installed
in /home/me/apps/phantomjs:
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py
index 14e9c1564..156d639b1 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py
@@ -39,7 +39,7 @@ from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.common.exceptions import NoSuchElementException, \
StaleElementReferenceException, TimeoutException
-def create_selenium_driver(browser='chrome'):
+def create_selenium_driver(cls,browser='chrome'):
# set default browser string based on env (if available)
env_browser = os.environ.get('TOASTER_TESTS_BROWSER')
if env_browser:
@@ -59,6 +59,15 @@ def create_selenium_driver(browser='chrome'):
return webdriver.Ie()
elif browser == 'phantomjs':
return webdriver.PhantomJS()
+ elif browser == 'remote':
+ # if we were to add yet another env variable like TOASTER_REMOTE_BROWSER
+ # we could let people pick firefox or chrome, left for later
+ remote_hub= os.environ.get('TOASTER_REMOTE_HUB')
+ driver = webdriver.Remote(remote_hub,
+ webdriver.DesiredCapabilities.FIREFOX.copy())
+
+ driver.get("http://%s:%s"%(cls.server_thread.host,cls.server_thread.port))
+ return driver
else:
msg = 'Selenium driver for browser %s is not available' % browser
raise RuntimeError(msg)
@@ -135,7 +144,7 @@ class SeleniumTestCaseBase(unittest.TestCase):
# instantiate the Selenium webdriver once for all the test methods
# in this test case
- cls.driver = create_selenium_driver()
+ cls.driver = create_selenium_driver(cls)
cls.driver.maximize_window()
@classmethod
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py
index 6392d1efb..f24fb093a 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py
@@ -91,9 +91,10 @@ class TestLayerDetailsPage(SeleniumTestCase):
for btn in self.find_all("dd .glyphicon-edit"):
btn.click()
- # Wait for the inputs to become visible
+ # Wait for the inputs to become visible after animation
self.wait_until_visible("#layer-git input[type=text]")
self.wait_until_visible("dd textarea")
+ self.wait_until_visible("dd .change-btn")
# Edit each value
for inputs in self.find_all("#layer-git input[type=text]") + \
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/buildtest.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/buildtest.py
index fc7bd5b64..5a56a110a 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/buildtest.py
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/buildtest.py
@@ -24,82 +24,115 @@ import sys
import time
import unittest
-from orm.models import Project, Release, ProjectTarget, Build
+from orm.models import Project, Release, ProjectTarget, Build, ProjectVariable
from bldcontrol.models import BuildEnvironment
-from bldcontrol.management.commands.loadconf import Command\
- as LoadConfigCommand
-
from bldcontrol.management.commands.runbuilds import Command\
as RunBuildsCommand
+from django.core.management import call_command
+
import subprocess
+import logging
+
+logger = logging.getLogger("toaster")
# We use unittest.TestCase instead of django.test.TestCase because we don't
# want to wrap everything in a database transaction as an external process
# (bitbake needs access to the database)
+def load_build_environment():
+ call_command('loaddata', 'settings.xml', app_label="orm")
+ call_command('loaddata', 'poky.xml', app_label="orm")
+
+ current_builddir = os.environ.get("BUILDDIR")
+ if current_builddir:
+ BuildTest.BUILDDIR = current_builddir
+ else:
+ # Setup a builddir based on default layout
+ # bitbake inside openebedded-core
+ oe_init_build_env_path = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)),
+ os.pardir,
+ os.pardir,
+ os.pardir,
+ os.pardir,
+ os.pardir,
+ 'oe-init-build-env'
+ )
+ if not os.path.exists(oe_init_build_env_path):
+ raise Exception("We had no BUILDDIR set and couldn't "
+ "find oe-init-build-env to set this up "
+ "ourselves please run oe-init-build-env "
+ "before running these tests")
+
+ oe_init_build_env_path = os.path.realpath(oe_init_build_env_path)
+ cmd = "bash -c 'source oe-init-build-env %s'" % BuildTest.BUILDDIR
+ p = subprocess.Popen(
+ cmd,
+ cwd=os.path.dirname(oe_init_build_env_path),
+ shell=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+
+ output, err = p.communicate()
+ p.wait()
+
+ logger.info("oe-init-build-env %s %s" % (output, err))
+
+ os.environ['BUILDDIR'] = BuildTest.BUILDDIR
+
+ # Setup the path to bitbake we know where to find this
+ bitbake_path = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)),
+ os.pardir,
+ os.pardir,
+ os.pardir,
+ os.pardir,
+ 'bin',
+ 'bitbake')
+ if not os.path.exists(bitbake_path):
+ raise Exception("Could not find bitbake at the expected path %s"
+ % bitbake_path)
+
+ os.environ['BBBASEDIR'] = bitbake_path
class BuildTest(unittest.TestCase):
PROJECT_NAME = "Testbuild"
+ BUILDDIR = "/tmp/build/"
def build(self, target):
# So that the buildinfo helper uses the test database'
self.assertEqual(
os.environ.get('DJANGO_SETTINGS_MODULE', ''),
- 'toastermain.settings-test',
+ 'toastermain.settings_test',
"Please initialise django with the tests settings: "
- "DJANGO_SETTINGS_MODULE='toastermain.settings-test'")
-
- if self.target_already_built(target):
- return
-
- # Take a guess at the location of the toasterconf
- poky_toaster_conf = '../../../meta-poky/conf/toasterconf.json'
- oe_toaster_conf = '../../../meta/conf/toasterconf.json'
- env_toaster_conf = os.environ.get('TOASTER_CONF')
+ "DJANGO_SETTINGS_MODULE='toastermain.settings_test'")
- config_file = None
- if env_toaster_conf:
- config_file = env_toaster_conf
- else:
- if os.path.exists(poky_toaster_conf):
- config_file = poky_toaster_conf
- elif os.path.exists(oe_toaster_conf):
- config_file = oe_toaster_conf
+ built = self.target_already_built(target)
+ if built:
+ return built
- self.assertIsNotNone(config_file,
- "Default locations for toasterconf not found"
- "please set $TOASTER_CONF manually")
-
- # Setup the release information and default layers
- print("\nImporting file: %s" % config_file)
- os.environ['TOASTER_CONF'] = config_file
- LoadConfigCommand()._import_layer_config(config_file)
-
- os.environ['TOASTER_DIR'] = \
- os.path.abspath(os.environ['BUILDDIR'] + "/../")
-
- os.environ['BBBASEDIR'] = \
- subprocess.check_output('which bitbake', shell=True)
+ load_build_environment()
BuildEnvironment.objects.get_or_create(
betype=BuildEnvironment.TYPE_LOCAL,
- sourcedir=os.environ['TOASTER_DIR'],
- builddir=os.environ['BUILDDIR']
+ sourcedir=BuildTest.BUILDDIR,
+ builddir=BuildTest.BUILDDIR
)
release = Release.objects.get(name='local')
# Create a project for this build to run in
- try:
- project = Project.objects.get(name=BuildTest.PROJECT_NAME)
- except Project.DoesNotExist:
- project = Project.objects.create_project(
- name=BuildTest.PROJECT_NAME,
- release=release
- )
+ project = Project.objects.create_project(name=BuildTest.PROJECT_NAME,
+ release=release)
+
+ if os.environ.get("TOASTER_TEST_USE_SSTATE_MIRROR"):
+ ProjectVariable.objects.get_or_create(
+ name="SSTATE_MIRRORS",
+ value="file://.* http://autobuilder.yoctoproject.org/pub/sstate/PATH;downloadfilename=PATH",
+ project=project)
ProjectTarget.objects.create(project=project,
target=target,
@@ -118,9 +151,11 @@ class BuildTest(unittest.TestCase):
sys.stdout.flush()
time.sleep(1)
- self.assertNotEqual(build_request.build.outcome,
- Build.SUCCEEDED, "Build did not SUCCEEDED")
- print("\nBuild finished")
+ self.assertEqual(Build.objects.get(pk=build_pk).outcome,
+ Build.SUCCEEDED,
+ "Build did not SUCCEEDED")
+
+ logger.info("\nBuild finished %s" % build_request.build.outcome)
return build_request.build
def target_already_built(self, target):
@@ -129,6 +164,6 @@ class BuildTest(unittest.TestCase):
project__name=BuildTest.PROJECT_NAME):
targets = build.target_set.values_list('target', flat=True)
if target in targets:
- return True
+ return build
- return False
+ return None
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/test_core_image_min.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/test_core_image_min.py
index dec0bfa7f..586f4a8f7 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/test_core_image_min.py
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/builds/test_core_image_min.py
@@ -31,14 +31,14 @@ from orm.models import Package_Dependency, Recipe_Dependency, Build
from orm.models import Task_Dependency, Package, Target, Recipe
from orm.models import CustomImagePackage
-from buildtest import BuildTest
+from tests.builds.buildtest import BuildTest
class BuildCoreImageMinimal(BuildTest):
"""Build core-image-minimal and test the results"""
def setUp(self):
- self.build("core-image-minimal")
+ self.completed_build = self.build("core-image-minimal")
# Check if build name is unique - tc_id=795
def test_Build_Unique_Name(self):
@@ -59,38 +59,29 @@ class BuildCoreImageMinimal(BuildTest):
# Check if task order is unique for one build - tc=824
def test_Task_Unique_Order(self):
- builds = Build.objects.values('id')
- cnt_err = []
-
- for build in builds:
- total_task_order = Task.objects.filter(
- build=build['id']).values('order').count()
- distinct_task_order = Task.objects.filter(
- build=build['id']).values('order').distinct().count()
+ total_task_order = Task.objects.filter(
+ build=self.built).values('order').count()
+ distinct_task_order = Task.objects.filter(
+ build=self.completed_build).values('order').distinct().count()
- if (total_task_order != distinct_task_order):
- cnt_err.append(build['id'])
-
- self.assertEqual(len(cnt_err),
- 0,
- msg='Errors for build id: %s' % cnt_err)
+ self.assertEqual(total_task_order,
+ distinct_task_order,
+ msg='Errors task order is not unique')
# Check task order sequence for one build - tc=825
def test_Task_Order_Sequence(self):
- builds = builds = Build.objects.values('id')
cnt_err = []
- for build in builds:
- tasks = Task.objects.filter(
- Q(build=build['id']),
- ~Q(order=None),
- ~Q(task_name__contains='_setscene')
- ).values('id', 'order').order_by("order")
+ tasks = Task.objects.filter(
+ Q(build=self.completed_build),
+ ~Q(order=None),
+ ~Q(task_name__contains='_setscene')
+ ).values('id', 'order').order_by("order")
- cnt_tasks = 0
- for task in tasks:
- cnt_tasks += 1
- if (task['order'] != cnt_tasks):
- cnt_err.append(task['id'])
+ cnt_tasks = 0
+ for task in tasks:
+ cnt_tasks += 1
+ if (task['order'] != cnt_tasks):
+ cnt_err.append(task['id'])
self.assertEqual(
len(cnt_err), 0, msg='Errors for task id: %s' % cnt_err)
@@ -126,8 +117,7 @@ class BuildCoreImageMinimal(BuildTest):
task['sstate_result'] != Task.SSTATE_MISS):
cnt_err.append({'id': task['id'],
'name': task['task_name'],
- 'sstate_result': task['sstate_result'],
- })
+ 'sstate_result': task['sstate_result']})
self.assertEqual(len(cnt_err),
0,
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/log/.create b/import-layers/yocto-poky/bitbake/lib/toaster/tests/commands/__init__.py
index e69de29bb..e69de29bb 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/contrib/tts/log/.create
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/commands/__init__.py
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/commands/test_loaddata.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/commands/test_loaddata.py
new file mode 100644
index 000000000..951f6ff5a
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/commands/test_loaddata.py
@@ -0,0 +1,61 @@
+#! /usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake Toaster Implementation
+#
+# Copyright (C) 2016 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from django.test import TestCase
+from django.core import management
+
+from orm.models import Layer_Version, Layer, Release, ToasterSetting
+
+
+class TestLoadDataFixtures(TestCase):
+ """ Test loading our 3 provided fixtures """
+ def test_run_loaddata_poky_command(self):
+ management.call_command('loaddata', 'poky')
+
+ num_releases = Release.objects.count()
+
+ self.assertTrue(
+ Layer_Version.objects.filter(
+ layer__name="meta-poky").count() == num_releases,
+ "Loaded poky fixture but don't have a meta-poky for all releases"
+ " defined")
+
+ def test_run_loaddata_oecore_command(self):
+ management.call_command('loaddata', 'oe-core')
+
+ # We only have the one layer for oe-core setup
+ self.assertTrue(
+ Layer.objects.filter(name="openembedded-core").count() > 0,
+ "Loaded oe-core fixture but still have no openemebedded-core"
+ " layer")
+
+ def test_run_loaddata_settings_command(self):
+ management.call_command('loaddata', 'settings')
+
+ self.assertTrue(
+ ToasterSetting.objects.filter(name="DEFAULT_RELEASE").count() > 0,
+ "Loaded settings but have no DEFAULT_RELEASE")
+
+ self.assertTrue(
+ ToasterSetting.objects.filter(
+ name__startswith="DEFCONF").count() > 0,
+ "Loaded settings but have no DEFCONF (default project "
+ "configuration values)")
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/commands/test_lsupdates.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/commands/test_lsupdates.py
new file mode 100644
index 000000000..49897a476
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/commands/test_lsupdates.py
@@ -0,0 +1,45 @@
+#! /usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake Toaster Implementation
+#
+# Copyright (C) 2016 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from django.test import TestCase
+from django.core import management
+
+from orm.models import Layer_Version, Machine, Recipe
+
+
+class TestLayerIndexUpdater(TestCase):
+ def test_run_lsupdates_command(self):
+ # Load some release information for us to fetch from the layer index
+ management.call_command('loaddata', 'poky')
+
+ old_layers_count = Layer_Version.objects.count()
+ old_recipes_count = Recipe.objects.count()
+ old_machines_count = Machine.objects.count()
+
+ # Now fetch the metadata from the layer index
+ management.call_command('lsupdates')
+
+ self.assertTrue(Layer_Version.objects.count() > old_layers_count,
+ "lsupdates ran but we still have no more layers!")
+ self.assertTrue(Recipe.objects.count() > old_recipes_count,
+ "lsupdates ran but we still have no more Recipes!")
+ self.assertTrue(Machine.objects.count() > old_machines_count,
+ "lsupdates ran but we still have no more Machines!")
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/commands/test_runbuilds.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/commands/test_runbuilds.py
new file mode 100644
index 000000000..3e634835e
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/commands/test_runbuilds.py
@@ -0,0 +1,88 @@
+#! /usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake Toaster Implementation
+#
+# Copyright (C) 2016 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import os
+
+from django.test import TestCase
+from django.core import management
+
+from orm.models import signal_runbuilds
+
+import threading
+import time
+import subprocess
+import signal
+
+
+class KillRunbuilds(threading.Thread):
+ """ Kill the runbuilds process after an amount of time """
+ def __init__(self, *args, **kwargs):
+ super(KillRunbuilds, self).__init__(*args, **kwargs)
+ self.setDaemon(True)
+
+ def run(self):
+ time.sleep(5)
+ signal_runbuilds()
+ time.sleep(1)
+
+ pidfile_path = os.path.join(os.environ.get("BUILDDIR", "."),
+ ".runbuilds.pid")
+
+ with open(pidfile_path) as pidfile:
+ pid = pidfile.read()
+ os.kill(int(pid), signal.SIGTERM)
+
+
+class TestCommands(TestCase):
+ """ Sanity test that runbuilds executes OK """
+
+ def setUp(self):
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE",
+ "toastermain.settings_test")
+ os.environ.setdefault("BUILDDIR",
+ "/tmp/")
+
+ # Setup a real database if needed for runbuilds process
+ # to connect to
+ management.call_command('migrate')
+
+ def test_runbuilds_command(self):
+ kill_runbuilds = KillRunbuilds()
+ kill_runbuilds.start()
+
+ manage_py = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)),
+ os.pardir,
+ os.pardir,
+ "manage.py")
+
+ command = "%s runbuilds" % manage_py
+
+ process = subprocess.Popen(command,
+ shell=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+
+ (out, err) = process.communicate()
+ process.wait()
+
+ self.assertNotEqual(process.returncode, 1,
+ "Runbuilds returned an error %s" % err)
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/eventreplay/README b/import-layers/yocto-poky/bitbake/lib/toaster/tests/eventreplay/README
new file mode 100644
index 000000000..8c5bb6432
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/eventreplay/README
@@ -0,0 +1,22 @@
+# Running eventreplay tests
+
+These tests use event log files produced by bitbake <target> -w <event log file>
+You need to have event log files produced before running this tests.
+
+At the moment of writing this document tests use 2 event log files: zlib.events
+and core-image-minimal.events. They're not provided with the tests due to their
+significant size.
+
+Here is how to produce them:
+
+$ . oe-init-build-env
+$ rm -r tmp sstate-cache
+$ bitbake core-image-minimal -w core-image-minimal.events
+$ rm -rf tmp sstate-cache
+$ bitbake zlib -w zlib.events
+
+After that it should be possible to run eventreplay tests this way:
+
+$ EVENTREPLAY_DIR=./ DJANGO_SETTINGS_MODULE=toastermain.settings_test ../bitbake/lib/toaster/manage.py test -v2 tests.eventreplay
+
+Note that environment variable EVENTREPLAY_DIR should point to the directory with event log files.
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/eventreplay/__init__.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/eventreplay/__init__.py
new file mode 100644
index 000000000..695661947
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/eventreplay/__init__.py
@@ -0,0 +1,97 @@
+#! /usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake Toaster Implementation
+#
+# Copyright (C) 2016 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# Tests were part of openembedded-core oe selftest Authored by: Lucian Musat
+# Ionut Chisanovici, Paul Eggleton and Cristian Iorga
+
+"""
+Test toaster backend by playing build event log files
+using toaster-eventreplay script
+"""
+
+import os
+
+from subprocess import getstatusoutput
+from pathlib import Path
+
+from django.test import TestCase
+
+from orm.models import Target_Installed_Package, Package, Build
+
+class EventReplay(TestCase):
+ """Base class for eventreplay test cases"""
+
+ def setUp(self):
+ """
+ Setup build environment:
+ - set self.script to toaster-eventreplay path
+ - set self.eventplay_dir to the value of EVENTPLAY_DIR env variable
+ """
+ bitbake_dir = Path(__file__.split('lib/toaster')[0])
+ self.script = bitbake_dir / 'bin' / 'toaster-eventreplay'
+ self.assertTrue(self.script.exists(), "%s doesn't exist")
+ self.eventplay_dir = os.getenv("EVENTREPLAY_DIR")
+ self.assertTrue(self.eventplay_dir,
+ "Environment variable EVENTREPLAY_DIR is not set")
+
+ def _replay(self, eventfile):
+ """Run toaster-eventplay <eventfile>"""
+ eventpath = Path(self.eventplay_dir) / eventfile
+ status, output = getstatusoutput('%s %s' % (self.script, eventpath))
+ if status:
+ print(output)
+
+ self.assertEqual(status, 0)
+
+class CoreImageMinimalEventReplay(EventReplay):
+ """Replay core-image-minimal events"""
+
+ def test_installed_packages(self):
+ """Test if all required packages have been installed"""
+
+ self._replay('core-image-minimal.events')
+
+ # test installed packages
+ packages = sorted(Target_Installed_Package.objects.\
+ values_list('package__name', flat=True))
+ self.assertEqual(packages, ['base-files', 'base-passwd', 'busybox',
+ 'busybox-hwclock', 'busybox-syslog',
+ 'busybox-udhcpc', 'eudev', 'glibc',
+ 'init-ifupdown', 'initscripts',
+ 'initscripts-functions', 'kernel-base',
+ 'kernel-module-uvesafb', 'libkmod',
+ 'modutils-initscripts', 'netbase',
+ 'packagegroup-core-boot', 'run-postinsts',
+ 'sysvinit', 'sysvinit-inittab',
+ 'sysvinit-pidof', 'udev-cache',
+ 'update-alternatives-opkg',
+ 'update-rc.d', 'util-linux-libblkid',
+ 'util-linux-libuuid', 'v86d', 'zlib'])
+
+class ZlibEventReplay(EventReplay):
+ """Replay zlib events"""
+
+ def test_replay_zlib(self):
+ """Test if zlib build and package are in the database"""
+ self._replay("zlib.events")
+
+ self.assertEqual(Build.objects.last().target_set.last().target, "zlib")
+ self.assertTrue('zlib' in Package.objects.values_list('name', flat=True))
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/functional/__init__.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/functional/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/functional/__init__.py
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/functional/functional_helpers.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/functional/functional_helpers.py
new file mode 100644
index 000000000..486078a61
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/functional/functional_helpers.py
@@ -0,0 +1,122 @@
+#! /usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake Toaster functional tests implementation
+#
+# Copyright (C) 2017 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import os
+import logging
+import subprocess
+import signal
+import time
+import re
+
+from tests.browser.selenium_helpers_base import SeleniumTestCaseBase
+from tests.builds.buildtest import load_build_environment
+
+logger = logging.getLogger("toaster")
+
+class SeleniumFunctionalTestCase(SeleniumTestCaseBase):
+ wait_toaster_time = 5
+
+ @classmethod
+ def setUpClass(cls):
+ # So that the buildinfo helper uses the test database'
+ if os.environ.get('DJANGO_SETTINGS_MODULE', '') != \
+ 'toastermain.settings_test':
+ raise RuntimeError("Please initialise django with the tests settings: " \
+ "DJANGO_SETTINGS_MODULE='toastermain.settings_test'")
+
+ load_build_environment()
+
+ # start toaster
+ cmd = "bash -c 'source toaster start'"
+ p = subprocess.Popen(
+ cmd,
+ cwd=os.environ.get("BUILDDIR"),
+ shell=True)
+ if p.wait() != 0:
+ raise RuntimeError("Can't initialize toaster")
+
+ super(SeleniumFunctionalTestCase, cls).setUpClass()
+ cls.live_server_url = 'http://localhost:8000/'
+
+ @classmethod
+ def tearDownClass(cls):
+ super(SeleniumFunctionalTestCase, cls).tearDownClass()
+
+ # XXX: source toaster stop gets blocked, to review why?
+ # from now send SIGTERM by hand
+ time.sleep(cls.wait_toaster_time)
+ builddir = os.environ.get("BUILDDIR")
+
+ with open(os.path.join(builddir, '.toastermain.pid'), 'r') as f:
+ toastermain_pid = int(f.read())
+ os.kill(toastermain_pid, signal.SIGTERM)
+ with open(os.path.join(builddir, '.runbuilds.pid'), 'r') as f:
+ runbuilds_pid = int(f.read())
+ os.kill(runbuilds_pid, signal.SIGTERM)
+
+
+ def get_URL(self):
+ rc=self.get_page_source()
+ project_url=re.search("(projectPageUrl\s:\s\")(.*)(\",)",rc)
+ return project_url.group(2)
+
+
+ def find_element_by_link_text_in_table(self, table_id, link_text):
+ """
+ Assume there're multiple suitable "find_element_by_link_text".
+ In this circumstance we need to specify "table".
+ """
+ try:
+ table_element = self.get_table_element(table_id)
+ element = table_element.find_element_by_link_text(link_text)
+ except NoSuchElementException as e:
+ print('no element found')
+ raise
+ return element
+
+ def get_table_element(self, table_id, *coordinate):
+ if len(coordinate) == 0:
+#return whole-table element
+ element_xpath = "//*[@id='" + table_id + "']"
+ try:
+ element = self.driver.find_element_by_xpath(element_xpath)
+ except NoSuchElementException as e:
+ raise
+ return element
+ row = coordinate[0]
+
+ if len(coordinate) == 1:
+#return whole-row element
+ element_xpath = "//*[@id='" + table_id + "']/tbody/tr[" + str(row) + "]"
+ try:
+ element = self.driver.find_element_by_xpath(element_xpath)
+ except NoSuchElementException as e:
+ return False
+ return element
+#now we are looking for an element with specified X and Y
+ column = coordinate[1]
+
+ element_xpath = "//*[@id='" + table_id + "']/tbody/tr[" + str(row) + "]/td[" + str(column) + "]"
+ try:
+ element = self.driver.find_element_by_xpath(element_xpath)
+ except NoSuchElementException as e:
+ return False
+ return element
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/functional/test_functional_basic.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/functional/test_functional_basic.py
new file mode 100644
index 000000000..cfa2b0fdf
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/functional/test_functional_basic.py
@@ -0,0 +1,243 @@
+#! /usr/bin/env python
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake Toaster functional tests implementation
+#
+# Copyright (C) 2017 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import time
+import re
+from tests.functional.functional_helpers import SeleniumFunctionalTestCase
+from orm.models import Project
+
+class FuntionalTestBasic(SeleniumFunctionalTestCase):
+
+# testcase (1514)
+ def test_create_slenium_project(self):
+ project_name = 'selenium-project'
+ self.get('')
+ self.driver.find_element_by_link_text("To start building, create your first Toaster project").click()
+ self.driver.find_element_by_id("new-project-name").send_keys(project_name)
+ self.driver.find_element_by_id('projectversion').click()
+ self.driver.find_element_by_id("create-project-button").click()
+ element = self.wait_until_visible('#project-created-notification')
+ self.assertTrue(self.element_exists('#project-created-notification'),'Project creation notification not shown')
+ self.assertTrue(project_name in element.text,
+ "New project name not in new project notification")
+ self.assertTrue(Project.objects.filter(name=project_name).count(),
+ "New project not found in database")
+
+ # testcase (1515)
+ def test_verify_left_bar_menu(self):
+ self.get('')
+ self.wait_until_visible('#projectstable')
+ self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
+ self.assertTrue(self.element_exists('#config-nav'),'Configuration Tab does not exist')
+ project_URL=self.get_URL()
+ self.driver.find_element_by_xpath('//a[@href="'+project_URL+'"]').click()
+
+ try:
+ self.driver.find_element_by_xpath("//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'customimages/"'+"]").click()
+ self.assertTrue(re.search("Custom images",self.driver.find_element_by_xpath("//div[@class='col-md-10']").text),'Custom images information is not loading properly')
+ except:
+ self.fail(msg='No Custom images tab available')
+
+ try:
+ self.driver.find_element_by_xpath("//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'images/"'+"]").click()
+ self.assertTrue(re.search("Compatible image recipes",self.driver.find_element_by_xpath("//div[@class='col-md-10']").text),'The Compatible image recipes information is not loading properly')
+ except:
+ self.fail(msg='No Compatible image tab available')
+
+ try:
+ self.driver.find_element_by_xpath("//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'softwarerecipes/"'+"]").click()
+ self.assertTrue(re.search("Compatible software recipes",self.driver.find_element_by_xpath("//div[@class='col-md-10']").text),'The Compatible software recipe information is not loading properly')
+ except:
+ self.fail(msg='No Compatible software recipe tab available')
+
+ try:
+ self.driver.find_element_by_xpath("//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'machines/"'+"]").click()
+ self.assertTrue(re.search("Compatible machines",self.driver.find_element_by_xpath("//div[@class='col-md-10']").text),'The Compatible machine information is not loading properly')
+ except:
+ self.fail(msg='No Compatible machines tab available')
+
+ try:
+ self.driver.find_element_by_xpath("//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'layers/"'+"]").click()
+ self.assertTrue(re.search("Compatible layers",self.driver.find_element_by_xpath("//div[@class='col-md-10']").text),'The Compatible layer information is not loading properly')
+ except:
+ self.fail(msg='No Compatible layers tab available')
+
+ try:
+ self.driver.find_element_by_xpath("//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'configuration"'+"]").click()
+ self.assertTrue(re.search("Bitbake variables",self.driver.find_element_by_xpath("//div[@class='col-md-10']").text),'The Bitbake variables information is not loading properly')
+ except:
+ self.fail(msg='No Bitbake variables tab available')
+
+# testcase (1516)
+ def test_review_configuration_information(self):
+ self.get('')
+ self.driver.find_element_by_xpath("//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
+ self.wait_until_visible('#projectstable')
+ self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
+ project_URL=self.get_URL()
+
+ try:
+ self.assertTrue(self.element_exists('#machine-section'),'Machine section for the project configuration page does not exist')
+ self.assertTrue(re.search("qemux86",self.driver.find_element_by_xpath("//span[@id='project-machine-name']").text),'The machine type is not assigned')
+ self.driver.find_element_by_xpath("//span[@id='change-machine-toggle']").click()
+ self.wait_until_visible('#select-machine-form')
+ self.wait_until_visible('#cancel-machine-change')
+ self.driver.find_element_by_xpath("//form[@id='select-machine-form']/a[@id='cancel-machine-change']").click()
+ except:
+ self.fail(msg='The machine information is wrong in the configuration page')
+
+ try:
+ self.driver.find_element_by_id('no-most-built')
+ except:
+ self.fail(msg='No Most built information in project detail page')
+
+ try:
+ self.assertTrue(re.search("Yocto Project master",self.driver.find_element_by_xpath("//span[@id='project-release-title']").text),'The project release is not defined')
+ except:
+ self.fail(msg='No project release title information in project detail page')
+
+ try:
+ self.driver.find_element_by_xpath("//div[@id='layer-container']")
+ self.assertTrue(re.search("3",self.driver.find_element_by_id("project-layers-count").text),'There should be 3 layers listed in the layer count')
+ layer_list = self.driver.find_element_by_id("layers-in-project-list")
+ layers = layer_list.find_elements_by_tag_name("li")
+ for layer in layers:
+ if re.match ("openembedded-core",layer.text):
+ print ("openembedded-core layer is a default layer in the project configuration")
+ elif re.match ("meta-poky",layer.text):
+ print ("meta-poky layer is a default layer in the project configuration")
+ elif re.match ("meta-yocto-bsp",layer.text):
+ print ("meta-yocto-bsp is a default layer in the project configuratoin")
+ else:
+ self.fail(msg='default layers are missing from the project configuration')
+ except:
+ self.fail(msg='No Layer information in project detail page')
+
+# testcase (1517)
+ def test_verify_machine_information(self):
+ self.get('')
+ self.driver.find_element_by_xpath("//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
+ self.wait_until_visible('#projectstable')
+ self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
+
+ try:
+ self.assertTrue(self.element_exists('#machine-section'),'Machine section for the project configuration page does not exist')
+ self.assertTrue(re.search("qemux86",self.driver.find_element_by_id("project-machine-name").text),'The machine type is not assigned')
+ self.driver.find_element_by_id("change-machine-toggle").click()
+ self.wait_until_visible('#select-machine-form')
+ self.wait_until_visible('#cancel-machine-change')
+ self.driver.find_element_by_id("cancel-machine-change").click()
+ except:
+ self.fail(msg='The machine information is wrong in the configuration page')
+
+# testcase (1518)
+ def test_verify_most_built_recipes_information(self):
+ self.get('')
+ self.driver.find_element_by_xpath("//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
+ self.wait_until_visible('#projectstable')
+ self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
+ project_URL=self.get_URL()
+
+ try:
+ self.assertTrue(re.search("You haven't built any recipes yet",self.driver.find_element_by_id("no-most-built").text),'Default message of no builds is not present')
+ self.driver.find_element_by_xpath("//div[@id='no-most-built']/p/a[@href="+'"'+project_URL+'images/"'+"]").click()
+ self.assertTrue(re.search("Compatible image recipes",self.driver.find_element_by_xpath("//div[@class='col-md-10']").text),'The Choose a recipe to build link is not working properly')
+ except:
+ self.fail(msg='No Most built information in project detail page')
+
+# testcase (1519)
+ def test_verify_project_release_information(self):
+ self.get('')
+ self.driver.find_element_by_xpath("//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
+ self.wait_until_visible('#projectstable')
+ self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
+
+ try:
+ self.assertTrue(re.search("Yocto Project master",self.driver.find_element_by_id("project-release-title").text),'The project release is not defined')
+ except:
+ self.fail(msg='No project release title information in project detail page')
+
+# testcase (1520)
+ def test_verify_layer_information(self):
+ self.get('')
+ self.driver.find_element_by_xpath("//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
+ self.wait_until_visible('#projectstable')
+ self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
+ project_URL=self.get_URL()
+
+ try:
+ self.driver.find_element_by_xpath("//div[@id='layer-container']")
+ self.assertTrue(re.search("3",self.driver.find_element_by_id("project-layers-count").text),'There should be 3 layers listed in the layer count')
+ layer_list = self.driver.find_element_by_id("layers-in-project-list")
+ layers = layer_list.find_elements_by_tag_name("li")
+
+ for layer in layers:
+ if re.match ("openembedded-core",layer.text):
+ print ("openembedded-core layer is a default layer in the project configuration")
+ elif re.match ("meta-poky",layer.text):
+ print ("meta-poky layer is a default layer in the project configuration")
+ elif re.match ("meta-yocto-bsp",layer.text):
+ print ("meta-yocto-bsp is a default layer in the project configuratoin")
+ else:
+ self.fail(msg='default layers are missing from the project configuration')
+
+ self.driver.find_element_by_xpath("//input[@id='layer-add-input']")
+ self.driver.find_element_by_xpath("//button[@id='add-layer-btn']")
+ self.driver.find_element_by_xpath("//div[@id='layer-container']/form[@class='form-inline']/p/a[@id='view-compatible-layers']")
+ self.driver.find_element_by_xpath("//div[@id='layer-container']/form[@class='form-inline']/p/a[@href="+'"'+project_URL+'importlayer"'+"]")
+ except:
+ self.fail(msg='No Layer information in project detail page')
+
+# testcase (1521)
+ def test_verify_project_detail_links(self):
+ self.get('')
+ self.driver.find_element_by_xpath("//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
+ self.wait_until_visible('#projectstable')
+ self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
+ project_URL=self.get_URL()
+
+ self.driver.find_element_by_xpath("//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li[@id='topbar-configuration-tab']/a[@href="+'"'+project_URL+'"'+"]").click()
+ self.assertTrue(re.search("Configuration",self.driver.find_element_by_xpath("//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li[@id='topbar-configuration-tab']/a[@href="+'"'+project_URL+'"'+"]").text), 'Configuration tab in project topbar is misspelled')
+
+ try:
+ self.driver.find_element_by_xpath("//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'builds/"'+"]").click()
+ self.assertTrue(re.search("Builds",self.driver.find_element_by_xpath("//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'builds/"'+"]").text), 'Builds tab in project topbar is misspelled')
+ self.driver.find_element_by_xpath("//div[@id='empty-state-projectbuildstable']")
+ except:
+ self.fail(msg='Builds tab information is not present')
+
+ try:
+ self.driver.find_element_by_xpath("//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'importlayer"'+"]").click()
+ self.assertTrue(re.search("Import layer",self.driver.find_element_by_xpath("//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'importlayer"'+"]").text), 'Import layer tab in project topbar is misspelled')
+ self.driver.find_element_by_xpath("//fieldset[@id='repo-select']")
+ self.driver.find_element_by_xpath("//fieldset[@id='git-repo']")
+ except:
+ self.fail(msg='Import layer tab not loading properly')
+
+ try:
+ self.driver.find_element_by_xpath("//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'newcustomimage/"'+"]").click()
+ self.assertTrue(re.search("New custom image",self.driver.find_element_by_xpath("//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li/a[@href="+'"'+project_URL+'newcustomimage/"'+"]").text), 'New custom image tab in project topbar is misspelled')
+ self.assertTrue(re.search("Select the image recipe you want to customise",self.driver.find_element_by_xpath("//div[@class='col-md-12']/h2").text),'The new custom image tab is not loading correctly')
+ except:
+ self.fail(msg='New custom image tab not loading properly')
+
+
+
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/views/README b/import-layers/yocto-poky/bitbake/lib/toaster/tests/views/README
new file mode 100644
index 000000000..950c7c989
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/views/README
@@ -0,0 +1,4 @@
+
+Django unit tests to verify classes and functions based on django Views
+
+To run just these tests use ./manage.py test tests.views
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/tests/views/__init__.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/views/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/views/__init__.py
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/tests.py b/import-layers/yocto-poky/bitbake/lib/toaster/tests/views/test_views.py
index 2b5894f74..1463077e9 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/tests.py
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/tests/views/test_views.py
@@ -24,31 +24,26 @@
from django.test import TestCase
from django.test.client import RequestFactory
from django.core.urlresolvers import reverse
-from django.utils import timezone
from django.db.models import Q
-from orm.models import Project, Release, BitbakeVersion, Package, LogMessage
-from orm.models import LayerSource, Layer, Build
-from orm.models import Layer_Version, Recipe, Machine, ProjectLayer, Target
-from orm.models import CustomImageRecipe, ProjectVariable
+from orm.models import Project, Package
+from orm.models import Layer_Version, Recipe
+from orm.models import CustomImageRecipe
from orm.models import CustomImagePackage
-import toastermain
import inspect
import toastergui
from toastergui.tables import SoftwareRecipesTable
import json
-from datetime import timedelta
from bs4 import BeautifulSoup
-import re
import string
-import json
PROJECT_NAME = "test project"
PROJECT_NAME2 = "test project 2"
CLI_BUILDS_PROJECT_NAME = 'Command line builds'
+
class ViewTests(TestCase):
"""Tests to verify view APIs."""
@@ -75,7 +70,8 @@ class ViewTests(TestCase):
url = reverse('all-projects')
response = self.client.get(url, {"format": "json"}, follow=True)
self.assertEqual(response.status_code, 200)
- self.assertTrue(response['Content-Type'].startswith('application/json'))
+ self.assertTrue(response['Content-Type'].startswith(
+ 'application/json'))
data = json.loads(response.content.decode('utf-8'))
@@ -98,13 +94,13 @@ class ViewTests(TestCase):
urls = [layers_url,
prj_url,
reverse('xhr_recipestypeahead', args=(self.project.id,)),
- reverse('xhr_machinestypeahead', args=(self.project.id,)),
- ]
+ reverse('xhr_machinestypeahead', args=(self.project.id,))]
def basic_reponse_check(response, url):
"""Check data structure of http response."""
self.assertEqual(response.status_code, 200)
- self.assertTrue(response['Content-Type'].startswith('application/json'))
+ self.assertTrue(response['Content-Type'].startswith(
+ 'application/json'))
data = json.loads(response.content.decode('utf-8'))
@@ -133,7 +129,6 @@ class ViewTests(TestCase):
return False
-
for url in urls:
results = False
@@ -147,35 +142,36 @@ class ViewTests(TestCase):
# from each of the urls
self.assertTrue(results)
- def test_xhr_import_layer(self):
- """Test xhr_importlayer API"""
- #Test for importing an already existing layer
- args = {'vcs_url' : "git://git.example.com/test",
- 'name' : "base-layer",
- 'git_ref': "c12b9596afd236116b25ce26dbe0d793de9dc7ce",
- 'project_id': self.project.id,
- 'local_source_dir': "",
- 'dir_path' : "/path/in/repository"}
- response = self.client.post(reverse('xhr_importlayer'), args)
+ def test_xhr_add_layer(self):
+ """Test xhr_add API"""
+ # Test for importing an already existing layer
+ api_url = reverse('xhr_layer', args=(self.project.id,))
+
+ layer_data = {'vcs_url': "git://git.example.com/test",
+ 'name': "base-layer",
+ 'git_ref': "c12b9596afd236116b25ce26dbe0d793de9dc7ce",
+ 'project_id': self.project.id,
+ 'local_source_dir': "",
+ 'add_to_project': True,
+ 'dir_path': "/path/in/repository"}
+
+ layer_data_json = json.dumps(layer_data)
+
+ response = self.client.put(api_url, layer_data_json)
data = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, 200)
self.assertEqual(data["error"], "ok")
- #Test to verify import of a layer successful
- args['name'] = "meta-oe"
- response = self.client.post(reverse('xhr_importlayer'), args)
- data = json.loads(response.content.decode('utf-8'))
- self.assertTrue(data["error"], "ok")
-
- #Test for html tag in the data
- args['<'] = "testing html tag"
- response = self.client.post(reverse('xhr_importlayer'), args)
- data = json.loads(response.content.decode('utf-8'))
- self.assertNotEqual(data["error"], "ok")
+ self.assertTrue(
+ layer_data['name'] in
+ self.project.get_all_compatible_layer_versions().values_list(
+ 'layer__name',
+ flat=True),
+ "Could not find imported layer in project's all layers list"
+ )
- #Empty data passed
- args = {}
- response = self.client.post(reverse('xhr_importlayer'), args)
+ # Empty data passed
+ response = self.client.put(api_url, "{}")
data = json.loads(response.content.decode('utf-8'))
self.assertNotEqual(data["error"], "ok")
@@ -232,15 +228,14 @@ class ViewTests(TestCase):
"info": {'id': self.customr.id,
'name': self.customr.name,
'base_recipe_id': self.recipe1.id,
- 'project_id': self.project.id,
- }
- }
- self.assertEqual(json.loads(response.content.decode('utf-8')), expected)
+ 'project_id': self.project.id}}
+ self.assertEqual(json.loads(response.content.decode('utf-8')),
+ expected)
def test_xhr_custom_del(self):
"""Test deleting custom recipe"""
name = "to be deleted"
- recipe = CustomImageRecipe.objects.create(\
+ recipe = CustomImageRecipe.objects.create(
name=name, project=self.project,
base_recipe=self.recipe1,
file_path="/tmp/testing",
@@ -259,7 +254,8 @@ class ViewTests(TestCase):
url = reverse('xhr_customrecipe_id', args=(recipe.id,))
response = self.client.delete(url)
self.assertEqual(response.status_code, 200)
- self.assertNotEqual(json.loads(response.content.decode('utf-8'))["error"], "ok")
+ self.assertNotEqual(json.loads(
+ response.content.decode('utf-8'))["error"], "ok")
def test_xhr_custom_packages(self):
"""Test adding and deleting package to a custom recipe"""
@@ -280,7 +276,8 @@ class ViewTests(TestCase):
response = self.client.delete(del_url)
self.assertEqual(response.status_code, 200)
- self.assertEqual(json.loads(response.content.decode('utf-8')), {"error": "ok"})
+ self.assertEqual(json.loads(response.content.decode('utf-8')),
+ {"error": "ok"})
all_packages = self.customr.get_all_packages().values_list('pk',
flat=True)
@@ -292,7 +289,8 @@ class ViewTests(TestCase):
response = self.client.delete(del_url)
self.assertEqual(response.status_code, 200)
- self.assertNotEqual(json.loads(response.content.decode('utf-8'))["error"], "ok")
+ self.assertNotEqual(json.loads(
+ response.content.decode('utf-8'))["error"], "ok")
def test_xhr_custom_packages_err(self):
"""Test error conditions of xhr_customrecipe_packages"""
@@ -303,8 +301,9 @@ class ViewTests(TestCase):
for method in (self.client.put, self.client.delete):
response = method(url)
self.assertEqual(response.status_code, 200)
- self.assertNotEqual(json.loads(response.content.decode('utf-8')),
- {"error": "ok"})
+ self.assertNotEqual(json.loads(
+ response.content.decode('utf-8')),
+ {"error": "ok"})
def test_download_custom_recipe(self):
"""Download the recipe file generated for the custom image"""
@@ -490,27 +489,28 @@ class ViewTests(TestCase):
# filter string to pass as the option
# This is the name of the filter:action
# e.g. project_filter:not_in_project
- filter_string = "%s:%s" % (column['filter_name'],
- filter_action['action_name'])
+ filter_string = "%s:%s" % (
+ column['filter_name'],
+ filter_action['action_name'])
# Now get the data with the filter applied
filtered_data = get_data(table_cls(),
- {"filter" : filter_string})
+ {"filter": filter_string})
# date range filter actions can't specify the
# number of results they return, so their count is 0
- if filter_action['count'] != None:
- self.assertEqual(len(filtered_data['rows']),
- int(filter_action['count']),
- "We added a table filter for %s but "
- "the number of rows returned was not "
- "what the filter info said there "
- "would be" % name)
-
+ if filter_action['count'] is not None:
+ self.assertEqual(
+ len(filtered_data['rows']),
+ int(filter_action['count']),
+ "We added a table filter for %s but "
+ "the number of rows returned was not "
+ "what the filter info said there "
+ "would be" % name)
# Test search functionality on the table
something_found = False
for search in list(string.ascii_letters):
- search_data = get_data(table_cls(), {'search' : search})
+ search_data = get_data(table_cls(), {'search': search})
if len(search_data['rows']) > 0:
something_found = True
@@ -521,20 +521,20 @@ class ViewTests(TestCase):
" was found for the search of table %s" % name)
# Test the limit functionality on the table
- limited_data = get_data(table_cls(), {'limit' : "1"})
+ limited_data = get_data(table_cls(), {'limit': "1"})
self.assertEqual(len(limited_data['rows']),
1,
"Limit 1 set on table %s but not 1 row returned"
% name)
# Test the pagination functionality on the table
- page_one_data = get_data(table_cls(), {'limit' : "1",
+ page_one_data = get_data(table_cls(), {'limit': "1",
"page": "1"})['rows'][0]
- page_two_data = get_data(table_cls(), {'limit' : "1",
+ page_two_data = get_data(table_cls(), {'limit': "1",
"page": "2"})['rows'][0]
self.assertNotEqual(page_one_data,
page_two_data,
- "Changed page on table %s but first row is the "
- "same as the previous page" % name)
+ "Changed page on table %s but first row is"
+ " the same as the previous page" % name)
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/api.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/api.py
index ae1f15077..1a6507c3f 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/api.py
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/api.py
@@ -20,6 +20,7 @@
import re
import logging
+import json
from collections import Counter
from orm.models import Project, ProjectTarget, Build, Layer_Version
@@ -136,14 +137,63 @@ class XhrBuildRequest(View):
class XhrLayer(View):
- """ Get and Update Layer information """
+ """ Delete, Get, Add and Update Layer information
+
+ Methods: GET POST DELETE PUT
+ """
+
+ def get(self, request, *args, **kwargs):
+ """
+ Get layer information
+
+ Method: GET
+ Entry point: /xhr_layer/<project id>/<layerversion_id>
+ """
+
+ try:
+ layer_version = Layer_Version.objects.get(
+ pk=kwargs['layerversion_id'])
+
+ project = Project.objects.get(pk=kwargs['pid'])
+
+ project_layers = ProjectLayer.objects.filter(
+ project=project).values_list("layercommit_id",
+ flat=True)
+
+ ret = {
+ 'error': 'ok',
+ 'id': layer_version.pk,
+ 'name': layer_version.layer.name,
+ 'layerdetailurl':
+ layer_version.get_detailspage_url(project.pk),
+ 'vcs_ref': layer_version.get_vcs_reference(),
+ 'vcs_url': layer_version.layer.vcs_url,
+ 'local_source_dir': layer_version.layer.local_source_dir,
+ 'layerdeps': {
+ "list": [
+ {
+ "id": dep.id,
+ "name": dep.layer.name,
+ "layerdetailurl":
+ dep.get_detailspage_url(project.pk),
+ "vcs_url": dep.layer.vcs_url,
+ "vcs_reference": dep.get_vcs_reference()
+ }
+ for dep in layer_version.get_alldeps(project.id)]
+ },
+ 'projectlayers': list(project_layers)
+ }
+
+ return JsonResponse(ret)
+ except Layer_Version.DoesNotExist:
+ error_response("No such layer")
def post(self, request, *args, **kwargs):
"""
Update a layer
- Entry point: /xhr_layer/<layerversion_id>
Method: POST
+ Entry point: /xhr_layer/<layerversion_id>
Args:
vcs_url, dirpath, commit, up_branch, summary, description,
@@ -201,9 +251,100 @@ class XhrLayer(View):
return error_response("Could not update layer version entry: %s"
% e)
- return JsonResponse({"error": "ok"})
+ return error_response("ok")
+
+ def put(self, request, *args, **kwargs):
+ """ Add a new layer
+
+ Method: PUT
+ Entry point: /xhr_layer/<project id>/
+ Args:
+ project_id, name,
+ [vcs_url, dir_path, git_ref], [local_source_dir], [layer_deps
+ (csv)]
+
+ """
+ try:
+ project = Project.objects.get(pk=kwargs['pid'])
+
+ layer_data = json.loads(request.body.decode('utf-8'))
+
+ # We require a unique layer name as otherwise the lists of layers
+ # becomes very confusing
+ existing_layers = \
+ project.get_all_compatible_layer_versions().values_list(
+ "layer__name",
+ flat=True)
+
+ add_to_project = False
+ layer_deps_added = []
+ if 'add_to_project' in layer_data:
+ add_to_project = True
+
+ if layer_data['name'] in existing_layers:
+ return JsonResponse({"error": "layer-name-exists"})
+
+ layer = Layer.objects.create(name=layer_data['name'])
+
+ layer_version = Layer_Version.objects.create(
+ layer=layer,
+ project=project,
+ layer_source=LayerSource.TYPE_IMPORTED)
+
+ # Local layer
+ if ('local_source_dir' in layer_data) and layer.local_source_dir:
+ layer.local_source_dir = layer_data['local_source_dir']
+ # git layer
+ elif 'vcs_url' in layer_data:
+ layer.vcs_url = layer_data['vcs_url']
+ layer_version.dirpath = layer_data['dir_path']
+ layer_version.commit = layer_data['git_ref']
+ layer_version.branch = layer_data['git_ref']
+
+ layer.save()
+ layer_version.save()
+
+ if add_to_project:
+ ProjectLayer.objects.get_or_create(
+ layercommit=layer_version, project=project)
+
+ # Add the layer dependencies
+ if 'layer_deps' in layer_data:
+ for layer_dep_id in layer_data['layer_deps'].split(","):
+ layer_dep = Layer_Version.objects.get(pk=layer_dep_id)
+ LayerVersionDependency.objects.get_or_create(
+ layer_version=layer_version, depends_on=layer_dep)
+
+ # Add layer deps to the project if specified
+ if add_to_project:
+ created, pl = ProjectLayer.objects.get_or_create(
+ layercommit=layer_dep, project=project)
+ layer_deps_added.append(
+ {'name': layer_dep.layer.name,
+ 'layerdetailurl':
+ layer_dep.get_detailspage_url(project.pk)})
+
+ except Layer_Version.DoesNotExist:
+ return error_response("layer-dep-not-found")
+ except Project.DoesNotExist:
+ return error_response("project-not-found")
+ except KeyError:
+ return error_response("incorrect-parameters")
+
+ return JsonResponse({'error': "ok",
+ 'imported_layer': {
+ 'name': layer.name,
+ 'layerdetailurl':
+ layer_version.get_detailspage_url()},
+ 'deps_added': layer_deps_added})
def delete(self, request, *args, **kwargs):
+ """ Delete an imported layer
+
+ Method: DELETE
+ Entry point: /xhr_layer/<projed id>/<layerversion_id>
+
+ """
try:
# We currently only allow Imported layers to be deleted
layer_version = Layer_Version.objects.get(
@@ -291,10 +432,13 @@ class XhrCustomRecipe(View):
return error_response("recipe-already-exists")
# create layer 'Custom layer' and verion if needed
- layer = Layer.objects.get_or_create(
+ layer, l_created = Layer.objects.get_or_create(
name=CustomImageRecipe.LAYER_NAME,
- summary="Layer for custom recipes",
- vcs_url="file:///toaster_created_layer")[0]
+ summary="Layer for custom recipes")
+
+ if l_created:
+ layer.local_source_dir = "toaster_created_layer"
+ layer.save()
# Check if we have a layer version already
# We don't use get_or_create here because the dirpath will change
@@ -303,9 +447,10 @@ class XhrCustomRecipe(View):
Q(layer=layer) &
Q(build=None)).last()
if lver is None:
- lver, created = Layer_Version.objects.get_or_create(
+ lver, lv_created = Layer_Version.objects.get_or_create(
project=params['project'],
layer=layer,
+ layer_source=LayerSource.TYPE_LOCAL,
dirpath="toaster_created_layer")
# Add a dependency on our layer to the base recipe's layer
@@ -319,7 +464,7 @@ class XhrCustomRecipe(View):
optional=False)
# Create the actual recipe
- recipe, created = CustomImageRecipe.objects.get_or_create(
+ recipe, r_created = CustomImageRecipe.objects.get_or_create(
name=request.POST["name"],
base_recipe=params["base"],
project=params["project"],
@@ -329,7 +474,7 @@ class XhrCustomRecipe(View):
# If we created the object then setup these fields. They may get
# overwritten later on and cause the get_or_create to create a
# duplicate if they've changed.
- if created:
+ if r_created:
recipe.file_path = request.POST["name"]
recipe.license = "MIT"
recipe.version = "0.1"
@@ -789,6 +934,9 @@ class XhrProject(View):
"url": layer.layercommit.layer.layer_index_url,
"layerdetailurl": layer.layercommit.get_detailspage_url(
project.pk),
+ "xhrLayerUrl": reverse("xhr_layer",
+ args=(project.pk,
+ layer.layercommit.pk)),
"layersource": layer.layercommit.layer_source
})
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/buildtables.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/buildtables.py
index dd0a6900d..755a7c2e4 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/buildtables.py
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/buildtables.py
@@ -571,6 +571,7 @@ class BuildTimeTable(BuildTasksTable):
super(BuildTimeTable, self).setup_columns(**kwargs)
self.columns[self.toggle_columns['order']]['hidden'] = True
+ self.columns[self.toggle_columns['order']]['hideable'] = True
self.columns[self.toggle_columns['sstate_result']]['hidden'] = True
self.columns[self.toggle_columns['elapsed_time']]['hidden'] = False
@@ -586,6 +587,7 @@ class BuildCPUTimeTable(BuildTasksTable):
super(BuildCPUTimeTable, self).setup_columns(**kwargs)
self.columns[self.toggle_columns['order']]['hidden'] = True
+ self.columns[self.toggle_columns['order']]['hideable'] = True
self.columns[self.toggle_columns['sstate_result']]['hidden'] = True
self.columns[self.toggle_columns['cpu_time_sys']]['hidden'] = False
self.columns[self.toggle_columns['cpu_time_user']]['hidden'] = False
@@ -602,5 +604,6 @@ class BuildIOTable(BuildTasksTable):
super(BuildIOTable, self).setup_columns(**kwargs)
self.columns[self.toggle_columns['order']]['hidden'] = True
+ self.columns[self.toggle_columns['order']]['hideable'] = True
self.columns[self.toggle_columns['sstate_result']]['hidden'] = True
self.columns[self.toggle_columns['disk_io']]['hidden'] = False
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/default.css b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/default.css
index ff24e8c1a..5cd7e211a 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/default.css
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/default.css
@@ -210,7 +210,6 @@ fieldset.fields-apart-from-layer-name { margin-top: 20px; }
#import-layer-name,
#layer-subdir { width: 20%; }
#layer-git-repo-url { width: 40%; }
-#layer-git-ref { width: 32%; }
#local-dir-path { width: 45%; }
#layer-dependency { width: 16em; }
#layer-deps-list { margin-top: 0; }
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/prettify.css b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/prettify.css
deleted file mode 100755
index b317a7cda..000000000
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/css/prettify.css
+++ /dev/null
@@ -1 +0,0 @@
-.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/customrecipe.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/customrecipe.js
index 9ea960288..8b1c190df 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/customrecipe.js
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/customrecipe.js
@@ -312,5 +312,11 @@ function customRecipePageInit(ctx) {
});
});
+ /* Stop the download link from working if it is in disabled state
+ * http://getbootstrap.com/css/#forms-disabled-fieldsets
+ */
+ $("a[disabled=disabled]").click(function(e){
+ e.preventDefault();
+ });
}
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/highlight.pack.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/highlight.pack.js
new file mode 100644
index 000000000..8cc886f1b
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/highlight.pack.js
@@ -0,0 +1,2 @@
+/*! highlight.js v9.12.0 | BSD3 License | git.io/hljslicense */
+!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset<r[0].offset?e:r:"start"===r[0].event?e:r:e.length?e:r}function o(e){function r(e){return" "+e.nodeName+'="'+n(e.value).replace('"',"&quot;")+'"'}s+="<"+t(e)+E.map.call(e.attributes,r).join("")+">"}function u(e){s+="</"+t(e)+">"}function c(e){("start"===e.event?o:u)(e.node)}for(var l=0,s="",f=[];e.length||r.length;){var g=i();if(s+=n(a.substring(l,g[0].offset)),l=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===l);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return s+n(a.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):x(a.k).forEach(function(e){u(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return l("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var c=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=c.length?t(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function l(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?"":I.classPrefix,i='<span class="'+a,o=t?"":C;return i+=e+'">',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=l(E,r),e?(B+=e[1],a+=p(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!y[E.sL])return n(k);var t=e?f(E.sL,k,!0,x[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(B+=t.r),e&&(x[E.sL]=t.top),p(t.language,t.value,!1,!0)}function b(){L+=null!=E.sL?d():h(),k=""}function v(e){L+=e.cN?p(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(k+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),b(),t.rB||t.eB||(k=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),b(),a.eE&&(k=n));do E.cN&&(L+=C),E.skip||(B+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"<unnamed>")+'"');return k+=n,n.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,E=i||N,x={},L="";for(R=E;R!==N;R=R.parent)R.cN&&(L=p(R.cN,"",!0)+L);var k="",B=0;try{for(var M,j,O=0;;){if(E.t.lastIndex=O,M=E.t.exec(t),!M)break;j=m(t.substring(O,M.index),M[0]),O=M.index+j}for(m(t.substr(O)),R=E;R.parent;R=R.parent)R.cN&&(L+=C);return{r:B,value:L,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function g(e,t){t=t||I.languages||x(y);var r={r:0,value:n(e)},a=r;return t.filter(w).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return I.tabReplace||I.useBR?e.replace(M,function(e,n){return I.useBR&&"\n"===e?"<br>":I.tabReplace?n.replace(/\t/g,I.tabReplace):""}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function d(e){var n,t,r,o,l,s=i(e);a(s)||(I.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(/<br[ \/]*>/g,"\n")):n=e,l=n.textContent,r=s?f(s,l,!0):g(l),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),l)),r.value=p(r.value),e.innerHTML=r.value,e.className=h(e.className,s,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){I=o(I,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=y[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function R(){return x(y)}function w(e){return e=(e||"").toLowerCase(),y[e]||y[L[e]]}var E=[],x=Object.keys,y={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="</span>",I={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=d,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("sql",function(e){var t=e.C("--","$");return{cI:!0,i:/[<>{}*#]/,c:[{bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment",e:/;/,eW:!0,l:/[\w\.]+/,k:{keyword:"abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias allocate allow alter always analyze ancillary and any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second section securefile security seed segment select self sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null",built_in:"array bigint binary bit blob boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text varchar varying void"},c:[{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[e.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[e.BE]},e.CNM,e.CBCM,t]},e.CBCM,t]}});hljs.registerLanguage("perl",function(e){var t="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when",r={cN:"subst",b:"[$@]\\{",e:"\\}",k:t},s={b:"->{",e:"}"},n={v:[{b:/\$\d/},{b:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{b:/[\$%@][^\s\w{]/,r:0}]},i=[e.BE,r,n],o=[n,e.HCM,e.C("^\\=\\w","\\=cut",{eW:!0}),s,{cN:"string",c:i,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",r:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",r:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",r:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",r:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",r:5},{b:"qw\\s+q",e:"q",r:5},{b:"'",e:"'",c:[e.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[e.BE]},{b:"{\\w+}",c:[],r:0},{b:"-?\\w+\\s*\\=\\>",c:[],r:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\/\\/|"+e.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",r:0,c:[e.HCM,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",r:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[e.BE],r:0}]},{cN:"function",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",eE:!0,r:5,c:[e.TM]},{b:"-\\w\\b",r:0},{b:"^__DATA__$",e:"^__END__$",sL:"mojolicious",c:[{b:"^@@.*",e:"$",cN:"comment"}]}];return r.c=o,s.c=o,{aliases:["pl","pm"],l:/[\w\.]+/,k:t,c:o}});hljs.registerLanguage("ini",function(e){var b={cN:"string",c:[e.BE],v:[{b:"'''",e:"'''",r:10},{b:'"""',e:'"""',r:10},{b:'"',e:'"'},{b:"'",e:"'"}]};return{aliases:["toml"],cI:!0,i:/\S/,c:[e.C(";","$"),e.HCM,{cN:"section",b:/^\s*\[+/,e:/\]+/},{b:/^[a-z0-9\[\]_-]+\s*=\s*/,e:"$",rB:!0,c:[{cN:"attr",b:/[a-z0-9\[\]_-]+/},{b:/=/,eW:!0,r:0,c:[{cN:"literal",b:/\bon|off|true|false|yes|no\b/},{cN:"variable",v:[{b:/\$[\w\d"][\w\d_]*/},{b:/\$\{(.*?)}/}]},b,{cN:"number",b:/([\+\-]+)?[\d]+_[\d_]+/},e.NM]}]}]}});hljs.registerLanguage("diff",function(e){return{aliases:["patch"],c:[{cN:"meta",r:10,v:[{b:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"comment",v:[{b:/Index: /,e:/$/},{b:/={3,}/,e:/$/},{b:/^\-{3}/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+{3}/,e:/$/},{b:/\*{5}/,e:/\*{5}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"addition",b:"^\\!",e:"$"}]}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},s={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/\b-?[a-z\._]+\b/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,s,a,t]}});hljs.registerLanguage("php",function(e){var c={b:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},i={cN:"meta",b:/<\?(php)?|\?>/},t={cN:"string",c:[e.BE,i],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},a={v:[e.BNM,e.CNM]};return{aliases:["php3","php4","php5","php6"],cI:!0,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[e.HCM,e.C("//","$",{c:[i]}),e.C("/\\*","\\*/",{c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.C("__halt_compiler.+?;",!1,{eW:!0,k:"__halt_compiler",l:e.UIR}),{cN:"string",b:/<<<['"]?\w+['"]?$/,e:/^\w+;?$/,c:[e.BE,{cN:"subst",v:[{b:/\$\w+/},{b:/\{\$/,e:/\}/}]}]},i,{cN:"keyword",b:/\$this\b/},c,{b:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{cN:"function",bK:"function",e:/[;{]/,eE:!0,i:"\\$|\\[|%",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",c,e.CBCM,t,a]}]},{cN:"class",bK:"class interface",e:"{",eE:!0,i:/[:\(\$"]/,c:[{bK:"extends implements"},e.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[e.UTM]},{bK:"use",e:";",c:[e.UTM]},{b:"=>"},t,a]}});hljs.registerLanguage("python",function(e){var r={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},b={cN:"meta",b:/^(>>>|\.\.\.) /},c={cN:"subst",b:/\{/,e:/\}/,k:r,i:/#/},a={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[b],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[b],r:10},{b:/(fr|rf|f)'''/,e:/'''/,c:[b,c]},{b:/(fr|rf|f)"""/,e:/"""/,c:[b,c]},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},{b:/(fr|rf|f)'/,e:/'/,c:[c]},{b:/(fr|rf|f)"/,e:/"/,c:[c]},e.ASM,e.QSM]},s={cN:"number",r:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},i={cN:"params",b:/\(/,e:/\)/,c:["self",b,s,a]};return c.c=[a,s,b],{aliases:["py","gyp"],k:r,i:/(<\/|->|\?)|=>/,c:[b,s,a,e.HCM,{v:[{cN:"function",bK:"def"},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,i,{b:/->/,eW:!0,k:"None"}]},{cN:"meta",b:/^[\t ]*@/,e:/$/},{b:/\b(print|exec)\(/}]}});hljs.registerLanguage("coffeescript",function(e){var c={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super yield import export from as default await then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",built_in:"npm require console print module global window document"},n="[A-Za-z$_][0-9A-Za-z$_]*",r={cN:"subst",b:/#\{/,e:/}/,k:c},i=[e.BNM,e.inherit(e.CNM,{starts:{e:"(\\s*/)?",r:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[e.BE]},{b:/'/,e:/'/,c:[e.BE]},{b:/"""/,e:/"""/,c:[e.BE,r]},{b:/"/,e:/"/,c:[e.BE,r]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[r,e.HCM]},{b:"//[gim]*",r:0},{b:/\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/}]},{b:"@"+n},{sL:"javascript",eB:!0,eE:!0,v:[{b:"```",e:"```"},{b:"`",e:"`"}]}];r.c=i;var s=e.inherit(e.TM,{b:n}),t="(\\(.*\\))?\\s*\\B[-=]>",o={cN:"params",b:"\\([^\\(]",rB:!0,c:[{b:/\(/,e:/\)/,k:c,c:["self"].concat(i)}]};return{aliases:["coffee","cson","iced"],k:c,i:/\/\*/,c:i.concat([e.C("###","###"),e.HCM,{cN:"function",b:"^\\s*"+n+"\\s*=\\s*"+t,e:"[-=]>",rB:!0,c:[s,o]},{b:/[:\(,=]\s*/,r:0,c:[{cN:"function",b:t,e:"[-=]>",rB:!0,c:[o]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:!0,i:/[:="\[\]]/,c:[s]},s]},{b:n+":",e:":",rB:!0,rE:!0,r:0}])}});hljs.registerLanguage("cpp",function(t){var e={cN:"keyword",b:"\\b[a-z\\d_]*_t\\b"},r={cN:"string",v:[{b:'(u8?|U)?L?"',e:'"',i:"\\n",c:[t.BE]},{b:'(u8?|U)?R"',e:'"',c:[t.BE]},{b:"'\\\\?.",e:"'",i:"."}]},s={cN:"number",v:[{b:"\\b(0b[01']+)"},{b:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{b:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],r:0},i={cN:"meta",b:/#\s*[a-z]+\b/,e:/$/,k:{"meta-keyword":"if else elif endif define undef warning error line pragma ifdef ifndef include"},c:[{b:/\\\n/,r:0},t.inherit(r,{cN:"meta-string"}),{cN:"meta-string",b:/<[^\n>]*>/,e:/$/,i:"\\n"},t.CLCM,t.CBCM]},a=t.IR+"\\s*\\(",c={keyword:"int float while private char catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignof constexpr decltype noexcept static_assert thread_local restrict _Bool complex _Complex _Imaginary atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and or not",built_in:"std string cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr",literal:"true false nullptr NULL"},n=[e,t.CLCM,t.CBCM,s,r];return{aliases:["c","cc","h","c++","h++","hpp"],k:c,i:"</",c:n.concat([i,{b:"\\b(deque|list|queue|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",e:">",k:c,c:["self",e]},{b:t.IR+"::",k:c},{v:[{b:/=/,e:/;/},{b:/\(/,e:/\)/},{bK:"new throw return else",e:/;/}],k:c,c:n.concat([{b:/\(/,e:/\)/,k:c,c:n.concat(["self"]),r:0}]),r:0},{cN:"function",b:"("+t.IR+"[\\*&\\s]+)+"+a,rB:!0,e:/[{;=]/,eE:!0,k:c,i:/[^\w\s\*&]/,c:[{b:a,rB:!0,c:[t.TM],r:0},{cN:"params",b:/\(/,e:/\)/,k:c,r:0,c:[t.CLCM,t.CBCM,r,s,e]},t.CLCM,t.CBCM,i]},{cN:"class",bK:"class struct",e:/[{;:]/,c:[{b:/</,e:/>/,c:["self"]},t.TM]}]),exports:{preprocessor:i,strings:r,k:c}}});hljs.registerLanguage("cs",function(e){var i={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long nameof object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let on orderby partial remove select set value var where yield",literal:"null false true"},t={cN:"string",b:'@"',e:'"',c:[{b:'""'}]},r=e.inherit(t,{i:/\n/}),a={cN:"subst",b:"{",e:"}",k:i},c=e.inherit(a,{i:/\n/}),n={cN:"string",b:/\$"/,e:'"',i:/\n/,c:[{b:"{{"},{b:"}}"},e.BE,c]},s={cN:"string",b:/\$@"/,e:'"',c:[{b:"{{"},{b:"}}"},{b:'""'},a]},o=e.inherit(s,{i:/\n/,c:[{b:"{{"},{b:"}}"},{b:'""'},c]});a.c=[s,n,t,e.ASM,e.QSM,e.CNM,e.CBCM],c.c=[o,n,r,e.ASM,e.QSM,e.CNM,e.inherit(e.CBCM,{i:/\n/})];var l={v:[s,n,t,e.ASM,e.QSM]},b=e.IR+"(<"+e.IR+"(\\s*,\\s*"+e.IR+")*>)?(\\[\\])?";return{aliases:["csharp"],k:i,i:/::/,c:[e.C("///","$",{rB:!0,c:[{cN:"doctag",v:[{b:"///",r:0},{b:"<!--|-->"},{b:"</?",e:">"}]}]}),e.CLCM,e.CBCM,{cN:"meta",b:"#",e:"$",k:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},l,e.CNM,{bK:"class interface",e:/[{;=]/,i:/[^\s:]/,c:[e.TM,e.CLCM,e.CBCM]},{bK:"namespace",e:/[{;=]/,i:/[^\s:]/,c:[e.inherit(e.TM,{b:"[a-zA-Z](\\.?\\w)*"}),e.CLCM,e.CBCM]},{cN:"meta",b:"^\\s*\\[",eB:!0,e:"\\]",eE:!0,c:[{cN:"meta-string",b:/"/,e:/"/}]},{bK:"new return throw await else",r:0},{cN:"function",b:"("+b+"\\s+)+"+e.IR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:i,c:[{b:e.IR+"\\s*\\(",rB:!0,c:[e.TM],r:0},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:i,r:0,c:[l,e.CNM,e.CBCM]},e.CLCM,e.CBCM]}]}});hljs.registerLanguage("shell",function(s){return{aliases:["console"],c:[{cN:"meta",b:"^\\s{0,3}[\\w\\d\\[\\]()@-]*[>%$#]",starts:{e:"$",sL:"bash"}}]}});hljs.registerLanguage("ruby",function(e){var b="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},c={cN:"doctag",b:"@[A-Za-z]+"},a={b:"#<",e:">"},s=[e.C("#","$",{c:[c]}),e.C("^\\=begin","^\\=end",{c:[c],r:10}),e.C("^__END__","\\n$")],n={cN:"subst",b:"#\\{",e:"}",k:r},t={cN:"string",c:[e.BE,n],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{b:/<<(-?)\w+$/,e:/^\s*\w+$/}]},i={cN:"params",b:"\\(",e:"\\)",endsParent:!0,k:r},d=[t,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{b:"<\\s*",c:[{b:"("+e.IR+"::)?"+e.IR}]}].concat(s)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:b}),i].concat(s)},{b:e.IR+"::"},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":(?!\\s)",c:[t,{b:b}],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{cN:"params",b:/\|/,e:/\|/,k:r},{b:"("+e.RSR+"|unless)\\s*",k:"unless",c:[a,{cN:"regexp",c:[e.BE,n],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(s),r:0}].concat(s);n.c=d,i.c=d;var l="[>?]>",o="[\\w#]+\\(\\w+\\):\\d+:\\d+>",u="(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>",w=[{b:/^\s*=>/,starts:{e:"$",c:d}},{cN:"meta",b:"^("+l+"|"+o+"|"+u+")",starts:{e:"$",c:d}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,i:/\/\*/,c:s.concat(w).concat(d)}});hljs.registerLanguage("nginx",function(e){var r={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+e.UIR}]},b={eW:!0,l:"[a-z/_]+",k:{literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},r:0,i:"=>",c:[e.HCM,{cN:"string",c:[e.BE,r],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{b:"([a-z]+):/",e:"\\s",eW:!0,eE:!0,c:[r]},{cN:"regexp",c:[e.BE,r],v:[{b:"\\s\\^",e:"\\s|{|;",rE:!0},{b:"~\\*?\\s+",e:"\\s|{|;",rE:!0},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",r:0},r]};return{aliases:["nginxconf"],c:[e.HCM,{b:e.UIR+"\\s+{",rB:!0,e:"{",c:[{cN:"section",b:e.UIR}],r:0},{b:e.UIR+"\\s",e:";|{",rB:!0,c:[{cN:"attribute",b:e.UIR,starts:b}],r:0}],i:"[^\\s\\}]"}});hljs.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",t={b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/},{b:/\(/,e:/\)/,c:[e.ASM,e.QSM]}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{b:"@",e:"[{;]",i:/:/,c:[{cN:"keyword",b:/\w+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:c,r:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,t]}]}});hljs.registerLanguage("makefile",function(e){var i={cN:"variable",v:[{b:"\\$\\("+e.UIR+"\\)",c:[e.BE]},{b:/\$[@%<?\^\+\*]/}]},r={cN:"string",b:/"/,e:/"/,c:[e.BE,i]},a={cN:"variable",b:/\$\([\w-]+\s/,e:/\)/,k:{built_in:"subst patsubst strip findstring filter filter-out sort word wordlist firstword lastword dir notdir suffix basename addsuffix addprefix join wildcard realpath abspath error warning shell origin flavor foreach if or and call eval file value"},c:[i]},n={b:"^"+e.UIR+"\\s*[:+?]?=",i:"\\n",rB:!0,c:[{b:"^"+e.UIR,e:"[:+?]?=",eE:!0}]},t={cN:"meta",b:/^\.PHONY:/,e:/$/,k:{"meta-keyword":".PHONY"},l:/[\.\w]+/},l={cN:"section",b:/^[^\s]+:/,e:/$/,c:[i]};return{aliases:["mk","mak"],k:"define endef undefine ifdef ifndef ifeq ifneq else endif include -include sinclude override export unexport private vpath",l:/[\w-]+/,c:[e.HCM,i,r,a,n,t,l]}});hljs.registerLanguage("java",function(e){var a="[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",t=a+"(<"+a+"(\\s*,\\s*"+a+")*>)?",r="false synchronized int abstract float private char boolean static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",s="\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",c={cN:"number",b:s,r:0};return{aliases:["jsp"],k:r,i:/<\/|#/,c:[e.C("/\\*\\*","\\*/",{r:0,c:[{b:/\w+@/,r:0},{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"new throw return else",r:0},{cN:"function",b:"("+t+"\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:r,c:[{b:e.UIR+"\\s*\\(",rB:!0,r:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:r,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},c,{cN:"meta",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("http",function(e){var t="HTTP/[0-9\\.]+";return{aliases:["https"],i:"\\S",c:[{b:"^"+t,e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{b:"^[A-Z]+ (.*?) "+t+"$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0},{b:t},{cN:"keyword",b:"[A-Z]+"}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{e:"$",r:0}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}});hljs.registerLanguage("objectivec",function(e){var t={cN:"built_in",b:"\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+"},_={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},i=/[a-zA-Z@][a-zA-Z0-9_]*/,n="@interface @class @protocol @implementation";return{aliases:["mm","objc","obj-c"],k:_,l:i,i:"</",c:[t,e.CLCM,e.CBCM,e.CNM,e.QSM,{cN:"string",v:[{b:'@"',e:'"',i:"\\n",c:[e.BE]},{b:"'",e:"[^\\\\]'",i:"[^\\\\][^']"}]},{cN:"meta",b:"#",e:"$",c:[{cN:"meta-string",v:[{b:'"',e:'"'},{b:"<",e:">"}]}]},{cN:"class",b:"("+n.split(" ").join("|")+")\\b",e:"({|$)",eE:!0,k:n,l:i,c:[e.UTM]},{b:"\\."+e.UIR,r:0}]}});hljs.registerLanguage("javascript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},a={cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},n={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},c={cN:"string",b:"`",e:"`",c:[e.BE,n]};n.c=[e.ASM,e.QSM,c,a,e.RM];var s=n.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx"],k:t,c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,c,e.CLCM,e.CBCM,a,{b:/[{,]\s*/,r:0,c:[{b:r+"\\s*:",rB:!0,r:0,c:[{cN:"attr",b:r,r:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+r+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:r},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:s}]}]},{b:/</,e:/(\/\w+|\w+\/)>/,sL:"xml",c:[{b:/<\w+\s*\/>/,skip:!0},{b:/<\w+/,e:/(\/\w+|\w+\/)>/,skip:!0,c:[{b:/<\w+\s*\/>/,skip:!0},"self"]}]}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:r}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:s}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage("apache",function(e){var r={cN:"number",b:"[\\$%]\\d+"};return{aliases:["apacheconf"],cI:!0,c:[e.HCM,{cN:"section",b:"</?",e:">"},{cN:"attribute",b:/\w+/,r:0,k:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,r:0,k:{literal:"on off all"},c:[{cN:"meta",b:"\\s\\[",e:"\\]$"},{cN:"variable",b:"[\\$%]\\{",e:"\\}",c:["self",r]},r,e.QSM]}}],i:/\S/}});hljs.registerLanguage("xml",function(s){var e="[A-Za-z0-9\\._:-]+",t={eW:!0,i:/</,r:0,c:[{cN:"attr",b:e,r:0},{b:/=\s*/,r:0,c:[{cN:"string",endsParent:!0,v:[{b:/"/,e:/"/},{b:/'/,e:/'/},{b:/[^\s"'=<>`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist"],cI:!0,c:[{cN:"meta",b:"<!DOCTYPE",e:">",r:10,c:[{b:"\\[",e:"\\]"}]},s.C("<!--","-->",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0}]},{cN:"tag",b:"<style(?=\\s|>|$)",e:">",k:{name:"style"},c:[t],starts:{e:"</style>",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"<script(?=\\s|>|$)",e:">",k:{name:"script"},c:[t],starts:{e:"</script>",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"meta",v:[{b:/<\?xml/,e:/\?>/,r:10},{b:/<\?\w+/,e:/\?>/}]},{cN:"tag",b:"</?",e:"/?>",c:[{cN:"name",b:/[^\/><\s]+/,r:0},t]}]}});hljs.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"section",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"quote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"^```w*s*$",e:"^```s*$"},{b:"`.+?`"},{b:"^( {4}| )",e:"$",r:0}]},{b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"string",b:"\\[",e:"\\]",eB:!0,rE:!0,r:0},{cN:"link",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"symbol",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],r:10},{b:/^\[[^\n]+\]:/,rB:!0,c:[{cN:"symbol",b:/\[/,e:/\]/,eB:!0,eE:!0},{cN:"link",b:/:\s*/,e:/$/,eB:!0}]}]}});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:n,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})],i:"\\S"},c={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return n.splice(n.length,0,t,c),{c:n,k:i,i:"\\S"}}); \ No newline at end of file
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/importlayer.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/importlayer.js
index 30dc28280..296483985 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/importlayer.js
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/importlayer.js
@@ -45,7 +45,7 @@ function importLayerPageInit (ctx) {
function(layer) {
if (layer.results.length > 0) {
currentLayerDepSelection = layer.results[0];
- layerDepBtn.click();
+ layerDepBtn.click();
}
});
@@ -158,6 +158,7 @@ function importLayerPageInit (ctx) {
project_id: libtoaster.ctx.projectId,
layer_deps: layerDepsCsv,
local_source_dir: $('#local-dir-path').val(),
+ add_to_project: true,
};
if ($('input[name=repo]:checked').val() == "git") {
@@ -168,13 +169,15 @@ function importLayerPageInit (ctx) {
}
$.ajax({
- type: "POST",
- url: ctx.xhrImportLayerUrl,
- data: layerData,
+ type: "PUT",
+ url: ctx.xhrLayerUrl,
+ data: JSON.stringify(layerData),
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (data) {
if (data.error != "ok") {
console.log(data.error);
+ /* let the user know why nothing happened */
+ alert(data.error)
} else {
createImportedNotification(data);
window.location.replace(libtoaster.ctx.projectPageUrl);
@@ -243,9 +246,18 @@ function importLayerPageInit (ctx) {
enable_import_btn(true);
}
- if ($("#git-repo-radio").prop("checked") &&
- vcsURLInput.val().length > 0 && gitRefInput.val().length > 0) {
- enable_import_btn(true);
+ if ($("#git-repo-radio").prop("checked")) {
+ if (gitRefInput.val().length > 0 &&
+ gitRefInput.val() == 'HEAD') {
+ $('#invalid-layer-revision-hint').show();
+ $('#layer-revision-ctrl').addClass('has-error');
+ enable_import_btn(false);
+ } else if (vcsURLInput.val().length > 0 &&
+ gitRefInput.val().length > 0) {
+ $('#invalid-layer-revision-hint').hide();
+ $('#layer-revision-ctrl').removeClass('has-error');
+ enable_import_btn(true);
+ }
}
}
@@ -332,19 +344,36 @@ function importLayerPageInit (ctx) {
check_form();
});
- /* Have a guess at the layer name */
+ /* Setup 'blank' typeahead */
+ libtoaster.makeTypeahead(gitRefInput,
+ ctx.xhrGitRevTypeAheadUrl,
+ { git_url: null }, function(){});
+
+
vcsURLInput.focusout(function (){
+ if (!$(this).val())
+ return;
+
/* If we a layer name specified don't overwrite it or if there isn't a
* url typed in yet return
*/
- if (layerNameInput.val() || !$(this).val())
- return;
-
- if ($(this).val().search("/")){
+ if (!layerNameInput.val() && $(this).val().search("/")){
var urlPts = $(this).val().split("/");
+ /* Add a suggestion of the layer name */
var suggestion = urlPts[urlPts.length-1].replace(".git","");
layerNameInput.val(suggestion);
}
+
+ /* Now actually setup the typeahead properly with the git url entered */
+ gitRefInput._typeahead('destroy');
+
+ libtoaster.makeTypeahead(gitRefInput,
+ ctx.xhrGitRevTypeAheadUrl,
+ { git_url: $(this).val() },
+ function(selected){
+ gitRefInput._typeahead("close");
+ });
+
});
function radioDisplay() {
@@ -389,7 +418,7 @@ function importLayerPageInit (ctx) {
var input = $(this);
var reBeginWithSlash = /^\//;
var reCheckVariable = /^\$/;
- var re = /([ <>\\|":\.%\?\*]+)/;
+ var re = /([ <>\\|":%\?\*]+)/;
var invalidDir = re.test(input.val());
var invalidSlash = reBeginWithSlash.test(input.val());
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/libtoaster.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/libtoaster.js
index 86662b7a6..6f9b5d0f0 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/libtoaster.js
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/libtoaster.js
@@ -274,9 +274,13 @@ var libtoaster = (function () {
}
function _addRmLayer(layerObj, add, doneCb){
+ if (layerObj.xhrLayerUrl === undefined){
+ throw("xhrLayerUrl is undefined")
+ }
+
if (add === true) {
/* If adding get the deps for this layer */
- libtoaster.getLayerDepsForProject(layerObj.layerdetailurl,
+ libtoaster.getLayerDepsForProject(layerObj.xhrLayerUrl,
function (layers) {
/* got result for dependencies */
@@ -542,11 +546,9 @@ $(document).ready(function() {
}
/*
- * PrettyPrint plugin.
- *
+ * highlight plugin.
*/
- // Init
- prettyPrint();
+ hljs.initHighlightingOnLoad();
// Prevent invalid links from jumping page scroll
$('a[href=#]').click(function() {
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/prettify.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/prettify.js
deleted file mode 100755
index eef5ad7e6..000000000
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/prettify.js
+++ /dev/null
@@ -1,28 +0,0 @@
-var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
-(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
-[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
-f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
-(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
-{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
-t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
-"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
-l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
-q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
-q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
-"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
-a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
-for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
-m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
-a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
-j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
-"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
-H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
-J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
-I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
-["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
-/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
-["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
-hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
-!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
-250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
-PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/projectpage.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/projectpage.js
index 453670364..21adf816c 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/projectpage.js
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/projectpage.js
@@ -12,6 +12,8 @@ function projectPageInit(ctx) {
var machineChangeFormToggle = $("#change-machine-toggle");
var machineNameTitle = $("#project-machine-name");
var machineChangeCancel = $("#cancel-machine-change");
+ var machineInputForm = $("#machine-input-form");
+ var invalidMachineNameHelp = $("#invalid-machine-name-help");
var freqBuildBtn = $("#freq-build-btn");
var freqBuildList = $("#freq-build-list");
@@ -208,8 +210,23 @@ function projectPageInit(ctx) {
/* Change machine functionality */
+ machineChangeInput.keyup(function(){
+ if ($(this).val().indexOf(' ') >= 0) {
+ machineChangeBtn.attr("disabled", "disabled");
+ invalidMachineNameHelp.show();
+ machineInputForm.addClass('has-error');
+ } else {
+ machineChangeBtn.removeAttr("disabled");
+ invalidMachineNameHelp.hide();
+ machineInputForm.removeClass('has-error');
+ }
+ });
machineChangeFormToggle.click(function(){
+ machineChangeInput.val(machineNameTitle.text());
+ machineChangeBtn.removeAttr("disabled");
+ invalidMachineNameHelp.hide();
+ machineInputForm.removeClass('has-error');
machineForm.slideDown();
machineNameTitle.hide();
$(this).hide();
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/table.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/table.js
index 176ce579f..abcb5ca7a 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/table.js
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/table.js
@@ -39,6 +39,8 @@ function tableInit(ctx){
', .show-all-'+ctx.tableName);
function loadData(tableParams){
+ table.trigger("table-loading");
+
$.ajax({
type: "GET",
url: ctx.url,
@@ -200,6 +202,7 @@ function tableInit(ctx){
}
/* Add table header and column toggle menu */
+ var column_edit_entries = [];
for (var i in tableData.columns){
var col = tableData.columns[i];
if (col.displayable === false) {
@@ -291,9 +294,17 @@ function tableInit(ctx){
defaultHiddenCols.push(col.field_name);
}
- editColMenu.append(toggler);
+ /* Gather the Edit Column entries */
+ column_edit_entries.push({'title':col.title,'html':toggler});
+
} /* End for each column */
+ /* Append the sorted Edit Column toggler entries */
+ column_edit_entries.sort(function(a,b) {return (a.title > b.title) ? 1 : ((b.title > a.title) ? -1 : 0);} );
+ for (var col in column_edit_entries){
+ editColMenu.append(column_edit_entries[col].html);
+ }
+
tableChromeDone = true;
}
@@ -835,4 +846,12 @@ function tableInit(ctx){
$('#filter-modal-'+ctx.tableName).modal('hide');
});
+
+ table.on("table-loading", function(){
+ table.css("opacity", 0.5);
+ });
+
+ table.on("table-done", function(){
+ table.css("opacity", 1);
+ })
}
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/tests/test.js b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/tests/test.js
index d7953de44..4a4c83f4c 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/tests/test.js
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/static/js/tests/test.js
@@ -5,6 +5,7 @@
QUnit.test("Layer alert notification", function(assert) {
var layer = {
"layerdetailurl":"/toastergui/project/1/layer/22",
+ "xhrLayerUrl":"/toastergui/xhr_layer/1/9",
"vcs_url":"git://example.com/example.git",
"detail":"[ git://example.com/example.git | master ]",
"vcs_reference":"master",
@@ -15,6 +16,7 @@ QUnit.test("Layer alert notification", function(assert) {
var layerDepsList = [
{
"layerdetailurl":"/toastergui/project/1/layer/9",
+ "xhrLayerUrl":"/toastergui/xhr_layer/1/9",
"vcs_url":"git://example.com/example.git",
"detail":"[ git://example.com/example.git | master ]",
"vcs_reference":"master",
@@ -23,6 +25,7 @@ QUnit.test("Layer alert notification", function(assert) {
},
{
"layerdetailurl":"/toastergui/project/1/layer/9",
+ "xhrLayerUrl":"/toastergui/xhr_layer/1/9",
"vcs_url":"git://example.com/example.git",
"detail":"[ git://example.com/example.git | master ]",
"vcs_reference":"master",
@@ -66,7 +69,8 @@ QUnit.test("Show notification", function(assert){
var layer = {
"id": 1,
"name": "meta-testing",
- "layerdetailurl": "/toastergui/project/1/layer/1"
+ "layerdetailurl": "/toastergui/project/1/layer/1",
+ "xhrLayerUrl": "/toastergui/xhr_layer/1/1"
};
QUnit.test("Add layer", function(assert){
@@ -144,7 +148,7 @@ QUnit.test("Make typeaheads", function(assert){
/* Page init functions */
QUnit.test("Import layer page init", function(assert){
- assert.throws(importLayerPageInit());
+ assert.throws(importLayerPageInit({ xhrGitRevTypeAheadUrl: "url" }));
});
QUnit.test("Project page init", function(assert){
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/base.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/base.html
index 496dd6eab..32b49795f 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/base.html
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/base.html
@@ -24,7 +24,7 @@
</script>
<script src="{% static 'js/jsrender.min.js' %}">
</script>
- <script src="{% static 'js/prettify.js' %}">
+ <script src="{% static 'js/highlight.pack.js' %}">
</script>
<script src="{% static 'js/libtoaster.js' %}">
</script>
@@ -100,7 +100,8 @@
<div class="collapse navbar-collapse" id="global-nav">
<ul class="nav navbar-nav">
{% if request.resolver_match.url_name != 'landing' and request.resolver_match.url_name != 'newproject' %}
- <li {% if request.resolver_match.url_name == 'all-builds' %}
+ <li id="navbar-all-builds"
+ {% if request.resolver_match.url_name == 'all-builds' %}
class="active"
{% endif %}>
<a href="{% url 'all-builds' %}">
@@ -108,7 +109,8 @@
All builds
</a>
</li>
- <li {% if request.resolver_match.url_name == 'all-projects' %}
+ <li id="navbar-all-projects"
+ {% if request.resolver_match.url_name == 'all-projects' %}
class="active"
{% endif %}>
<a href="{% url 'all-projects' %}">
@@ -117,7 +119,7 @@
</a>
</li>
{% endif %}
- <li>
+ <li id="navbar-docs">
<a target="_blank" href="http://www.yoctoproject.org/docs/latest/toaster-manual/toaster-manual.html">
<i class="glyphicon glyphicon-book"></i>
Documentation
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basebuildpage.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basebuildpage.html
index f5eee9651..a41911f52 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basebuildpage.html
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/basebuildpage.html
@@ -132,16 +132,16 @@
{% endif %}
<li class="nav-header">Build</li>
<li id="menu-configuration"><a href="{% url 'configuration' build.pk %}">Configuration</a></li>
- <li><a href="{% url 'tasks' build.pk %}">Tasks</a></li>
- <li><a href="{% url 'recipes' build.pk %}">Recipes</a></li>
- <li><a href="{% url 'packages' build.pk %}">Packages</a></li>
+ <li id="menu-tasks"><a href="{% url 'tasks' build.pk %}">Tasks</a></li>
+ <li id="menu-recipes"><a href="{% url 'recipes' build.pk %}">Recipes</a></li>
+ <li id="menu-packages"><a href="{% url 'packages' build.pk %}">Packages</a></li>
<li class="nav-header">Performance</li>
- <li><a href="{% url 'buildtime' build.pk %}">Time</a></li>
- <li><a href="{% url 'cputime' build.pk %}">CPU usage</a></li>
- <li><a href="{% url 'diskio' build.pk %}">Disk I/O</a></li>
+ <li id="menu-time"><a href="{% url 'buildtime' build.pk %}">Time</a></li>
+ <li id="menu-cpu-time"><a href="{% url 'cputime' build.pk %}">CPU usage</a></li>
+ <li id="menu-disk-io"><a href="{% url 'diskio' build.pk %}">Disk I/O</a></li>
<li class="nav-header">Actions</li>
- <li>
+ <li id="menu-download-build-log">
<a href="{% url 'build_artifact' build.id 'cookerlog' build.id %}">
<span class="glyphicon glyphicon-download-alt"></span>
Download build log
@@ -151,7 +151,7 @@
{% with build.get_custom_image_recipes as custom_image_recipes %}
{% if custom_image_recipes.count > 0 %}
<!-- edit custom image built during this build -->
- <li>
+ <li id="menu-edit-custom-image">
<a href="#" data-role="edit-custom-image-trigger">
<span class="glyphicon glyphicon-edit"></span>
Edit custom image
@@ -186,7 +186,7 @@
<!-- new custom image from image recipe in this build -->
{% if build.has_image_recipes %}
- <li>
+ <li id="menu-new-custom-image">
<a href="#" data-role="new-custom-image-trigger">
<span class="glyphicon glyphicon-plus"></span>
New custom image
@@ -216,7 +216,7 @@
</script>
{% endif %}
- <li>
+ <li id="menu-delete-build">
<a href="#delete-build-modal" id="delete-build" data-toggle="modal" data-target="#delete-build-modal" class="text-danger">
<span class="glyphicon glyphicon-trash"></span>
Delete build
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/importlayer.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/importlayer.html
index 1f426969a..97d52c76c 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/importlayer.html
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/importlayer.html
@@ -13,7 +13,8 @@
<script>
$(document).ready(function (){
var ctx = {
- xhrImportLayerUrl : "{% url 'xhr_importlayer' %}",
+ xhrLayerUrl : "{% url 'xhr_layer' project.id %}",
+ xhrGitRevTypeAheadUrl : "{% url 'xhr_gitrevtypeahead' %}",
};
try {
@@ -112,8 +113,10 @@
Git revision
<span class="glyphicon glyphicon-question-sign get-help" title="You can provide a Git branch, a tag or a commit SHA as the revision"></span>
</label>
- <input type="text" class="form-control" id="layer-git-ref" required>
- <span class="help-inline" style="display:none;" id="invalid-layer-revision-hint"></span>
+ <span style="display: block">
+ <input type="text" class="form-control" id="layer-git-ref" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead" required>
+ </span>
+ <span class="help-block has-error" style="display:none;" id="invalid-layer-revision-hint">The "HEAD" branch is reserved (only allowed for the "Local Yocto Project" layers)</span>
</div>
</fieldset>
@@ -123,7 +126,7 @@
<label for="local-dir-path" class="control-label">Enter the absolute path to the layer directory</label>
<input type="text" class="form-control" id="local-dir-path" required/>
<p class="help-block" id="hintError-dir-path-starts-with-slash" style="display:none;">The absolute path must start with "/".</p>
- <p class="help-block" id="hintError-dir-path" style="display:none;">The directory path cannot include spaces or any of these characters: . \ ? % * : | " " &lt; &gt;</p>
+ <p class="help-block" id="hintError-dir-path" style="display:none;">The directory path cannot include spaces or any of these characters: \ ? % * : | " " &lt; &gt;</p>
</div>
</fieldset>
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/js-unit-tests.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/js-unit-tests.html
index 94ad4f8e9..ca248962f 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/js-unit-tests.html
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/js-unit-tests.html
@@ -14,7 +14,7 @@
<script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/filtersnippet.js' %}"></script>
<script src="{% static 'js/importlayer.js' %}"></script>
-<script src="{% static 'js/prettify.js' %}"></script>
+<script src="{% static 'js/highlight.pack.js' %}"></script>
<script src="{% static 'js/layerBtn.js' %}"></script>
<script src="{% static 'js/layerDepsModal.js' %}"></script>
<script src="{% static 'js/projectpage.js' %}"></script>
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/layer_btn.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/layer_btn.html
index b2f73eba7..1580991a1 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/layer_btn.html
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/layer_btn.html
@@ -1,4 +1,9 @@
-<a class="btn btn-danger btn-block layer-exists-{{data.pk}} layerbtn" data-layer='{ "id": {{data.pk}}, "name": "{{data.layer.name}}", "layerdetailurl": "{%url 'layerdetails' extra.pid data.pk%}"}' data-directive="remove"
+<a class="btn btn-danger btn-block layer-exists-{{data.pk}} layerbtn" data-layer='{
+ "id": {{data.pk}},
+ "name": "{{data.layer.name}}",
+ "xhrLayerUrl": "{% url "xhr_layer" extra.pid data.pk %}",
+ "layerdetailurl": "{% url 'layerdetails' extra.pid data.pk %}"
+ }' data-directive="remove"
{% if data.pk not in extra.current_layers %}
style="display:none;"
{% endif %}
@@ -6,7 +11,12 @@
<span class="glyphicon glyphicon-trash"></span>
Remove layer
</a>
-<a class="btn btn-default btn-block layer-add-{{data.pk}} layerbtn" data-layer='{ "id": {{data.pk}}, "name": "{{data.layer.name}}", "layerdetailurl": "{%url 'layerdetails' extra.pid data.pk%}"}' data-directive="add"
+<a class="btn btn-default btn-block layer-add-{{data.pk}} layerbtn"
+ data-layer='{ "id": {{data.pk}},
+ "name": "{{data.layer.name}}",
+ "xhrLayerUrl": "{% url "xhr_layer" extra.pid data.pk %}",
+ "layerdetailurl": "{%url "layerdetails" extra.pid data.pk %}"
+ }' data-directive="add"
{% if data.pk in extra.current_layers %}
style="display:none;"
{% endif %}
@@ -14,4 +24,3 @@
<span class="glyphicon glyphicon-plus"></span>
Add layer
</a>
-
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/layerdetails.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/layerdetails.html
index f1569bd63..e0069db80 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/layerdetails.html
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/layerdetails.html
@@ -71,6 +71,7 @@
inCurrentPrj : false,
{% endif %}
layerdetailurl : "{% url 'layerdetails' project.id layerversion.id %}",
+ xhrLayerUrl: "{% url 'xhr_layer' project.id layerversion.id %}",
layer_source: {{layerversion.layer_source|json}},
},
layerSourceTypes: {{layer_source|json}},
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/machine_btn.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/machine_btn.html
index 5d93d7aa9..6debe6573 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/machine_btn.html
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/machine_btn.html
@@ -4,7 +4,12 @@
{% endif %}
>
Select machine</a>
-<a class="btn btn-default btn-block layerbtn layer-add-{{data.layer_version.id}}" data-layer='{ "id": {{data.layer_version.id}}, "name": "{{data.layer_version.layer.name}}", "layerdetailurl": "{%url 'layerdetails' extra.pid data.layer_version.id %}"}' data-directive="add"
+<a class="btn btn-default btn-block layerbtn
+layer-add-{{data.layer_version.id}}" data-layer='{ "id":
+ {{data.layer_version.id}}, "name": "{{data.layer_version.layer.name}}",
+ "layerdetailurl": "{%url 'layerdetails' extra.pid data.layer_version.id %}",
+ "xhrLayerUrl": "{% url "xhr_layer" extra.pid data.layer_version.id %}"
+ }' data-directive="add"
{% if data.layer_version.pk in extra.current_layers %}
style="display:none;"
{% endif %}
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/project.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/project.html
index 5abe24130..ab7e665b6 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/project.html
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/project.html
@@ -67,11 +67,12 @@
<form id="select-machine-form" style="display:none;" class="form-inline">
<span class="help-block">Machine suggestions come from the list of layers added to your project. If you don't see the machine you are looking for, <a href="{% url 'projectmachines' project.id %}">check the full list of machines</a></span>
- <div class="form-group">
+ <div class="form-group" id="machine-input-form">
<input class="form-control" id="machine-change-input" autocomplete="off" value="" data-provide="typeahead" data-minlength="1" data-autocomplete="off" type="text">
</div>
<button id="machine-change-btn" class="btn btn-default" type="button">Save</button>
<a href="#" id="cancel-machine-change" class="btn btn-link">Cancel</a>
+ <span class="help-block text-danger" id="invalid-machine-name-help" style="display:none">A valid machine name cannot include spaces.</span>
<p class="form-link"><a href="{% url 'projectmachines' project.id %}">View compatible machines</a></p>
</form>
</div>
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projectconf.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projectconf.html
index fcf6df2bf..933c588f3 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projectconf.html
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/projectconf.html
@@ -41,7 +41,7 @@
</div>
<button id="apply-change-dl_dir" class="btn btn-default" type="button">Save</button>
<button id="cancel-change-dl_dir" type="button" class="btn btn-link">Cancel</button>
- <p class="help-block" id="hintError-dl_dir" style="display:none;">The directory path cannot include spaces or any of these characters: . \ ? % * : | " " &lt; &gt;</p>
+ <p class="help-block" id="hintError-dl_dir" style="display:none;">The directory path cannot include spaces or any of these characters: \ ? % * : | " " &lt; &gt;</p>
<p class="help-block" id="hintError-initialChar-dl_dir" style="display:none;">The directory path should either start with a /, e.g. /home/toaster/downloads; or with a variable, e.g. ${TOPDIR}/downloads.</p>
</form>
</dd>
@@ -63,6 +63,7 @@
<button id="cancel-change-image_fstypes" type="button" class="btn btn-link">Cancel</button>
</div>
<p class="help-block text-danger" style="display:none;" id="hintError-image-fs_type">A valid image type cannot include underscores</p>
+ <p class="help-block text-danger" style="display:none;" id="fstypes-error-message">You must select at least one image type</p>
<label>Or choose from known image types:</label>
<input id="filter-image_fstypes" type="text" placeholder="Search image types" class="form-control">
<div id="all-image_fstypes" class="scrolling"></div>
@@ -150,7 +151,7 @@
</div>
<button id="apply-change-sstate_dir" class="btn btn-default" type="button">Save</button>
<button id="cancel-change-sstate_dir" type="button" class="btn btn-link">Cancel</button>
- <p class="help-block" id="hintError-sstate_dir" style="display:none;">The directory path cannot include spaces or any of these characters: . \ ? % * : | " " &lt; &gt;</p>
+ <p class="help-block" id="hintError-sstate_dir" style="display:none;">The directory path cannot include spaces or any of these characters: \ ? % * : | " " &lt; &gt;</p>
<p class="help-block" id="hintError-initialChar-sstate_dir" style="display:none;">The directory path should either start with a /, e.g. /home/toaster/sstate-cache; or with a variable, e.g. ${TOPDIR}/sstate-cache.</p>
</form>
</dd>
@@ -593,7 +594,7 @@ $(document).ready(function() {
var input = $(this);
var reBeginWithSlash = /^\//;
var reCheckVariable = /^\$/;
- var re = /([ <>\\|":\.%\?\*]+)/;
+ var re = /([ <>\\|":%\?\*]+)/;
var invalidDir = re.test(input.val());
var invalidSlash = reBeginWithSlash.test(input.val());
var invalidVar = reCheckVariable.test(input.val());
@@ -716,8 +717,10 @@ $(document).ready(function() {
}
if ($('#new-imagefs_types').val().length === 0) {
$("#apply-change-image_fstypes").prop("disabled", true);
+ $('#fstypes-error-message').show();
} else {
$("#apply-change-image_fstypes").prop("disabled", false);
+ $('#fstypes-error-message').hide();
}
});
@@ -958,7 +961,7 @@ $(document).ready(function() {
var input = $(this);
var reBeginWithSlash = /^\//;
var reCheckVariable = /^\$/;
- var re = /([ <>\\|":\.%\?\*]+)/;
+ var re = /([ <>\\|":%\?\*]+)/;
var invalidDir = re.test(input.val());
var invalidSlash = reBeginWithSlash.test(input.val());
var invalidVar = reCheckVariable.test(input.val());
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipe_btn.html b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipe_btn.html
index e3729643a..0ee0ba559 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipe_btn.html
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/templates/recipe_btn.html
@@ -5,7 +5,13 @@
>
Build recipe
</a>
-<a class="btn btn-default btn-block layerbtn layer-add-{{data.layer_version.pk}}" data-layer='{ "id": {{data.layer_version.pk}}, "name": "{{data.layer_version.layer.name}}", "layerdetailurl": "{%url 'layerdetails' extra.pid data.layer_version.pk%}"}' data-directive="add"
+<a class="btn btn-default btn-block layerbtn layer-add-{{data.layer_version.pk}}"
+ data-layer='{
+ "id": {{data.layer_version.pk}},
+ "name": "{{data.layer_version.layer.name}}",
+ "layerdetailurl": "{%url "layerdetails" extra.pid data.layer_version.pk%}",
+ "xhrLayerUrl": "{% url "xhr_layer" extra.pid data.layer_version.pk %}"
+ }' data-directive="add"
{% if data.layer_version.pk in extra.current_layers %}
style="display:none;"
{% endif %}
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/typeaheads.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/typeaheads.py
index 4ded9ac2e..58c650f8f 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/typeaheads.py
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/typeaheads.py
@@ -16,15 +16,17 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+import subprocess
+
from toastergui.widgets import ToasterTypeAhead
from orm.models import Project
from django.core.urlresolvers import reverse
+from django.core.cache import cache
+
class LayersTypeAhead(ToasterTypeAhead):
""" Typeahead for layers available and not added in the current project's
configuration """
- def __init__(self):
- super(LayersTypeAhead, self).__init__()
def apply_search(self, search_term, prj, request):
layers = prj.get_all_compatible_layer_versions()
@@ -33,13 +35,15 @@ class LayersTypeAhead(ToasterTypeAhead):
# Unlike the other typeaheads we also don't want to show suggestions
# for layers already in the project unless required such as when adding
# layerdeps to a new layer.
- if ("include_added" in request.GET and
- request.GET['include_added'] != "true"):
+ if "include_added" in request.GET and \
+ request.GET['include_added'] != "true":
layers = layers.exclude(
pk__in=prj.get_project_layer_versions(pk=True))
primary_results = layers.filter(layer__name__istartswith=search_term)
- secondary_results = layers.filter(layer__name__icontains=search_term).exclude(pk__in=primary_results)
+ secondary_results = layers.filter(
+ layer__name__icontains=search_term).exclude(
+ pk__in=primary_results)
results = []
@@ -49,99 +53,129 @@ class LayersTypeAhead(ToasterTypeAhead):
detail = "[ %s | %s ]" % (layer_version.layer.vcs_url,
vcs_reference)
needed_fields = {
- 'id' : layer_version.pk,
- 'name' : layer_version.layer.name,
- 'layerdetailurl' : layer_version.get_detailspage_url(prj.pk),
- 'vcs_url' : layer_version.layer.vcs_url,
- 'vcs_reference' : vcs_reference,
- 'detail' : detail,
- 'local_source_dir' : layer_version.layer.local_source_dir,
+ 'id': layer_version.pk,
+ 'name': layer_version.layer.name,
+ 'layerdetailurl': layer_version.get_detailspage_url(prj.pk),
+ 'xhrLayerUrl': reverse('xhr_layer',
+ args=(prj.pk, layer_version.pk)),
+ 'vcs_url': layer_version.layer.vcs_url,
+ 'vcs_reference': vcs_reference,
+ 'detail': detail,
+ 'local_source_dir': layer_version.layer.local_source_dir,
}
results.append(needed_fields)
return results
+
class MachinesTypeAhead(ToasterTypeAhead):
""" Typeahead for all the machines available in the current project's
configuration """
- def __init__(self):
- super(MachinesTypeAhead, self).__init__()
def apply_search(self, search_term, prj, request):
machines = prj.get_available_machines()
machines = machines.order_by("name")
primary_results = machines.filter(name__istartswith=search_term)
- secondary_results = machines.filter(name__icontains=search_term).exclude(pk__in=primary_results)
- tertiary_results = machines.filter(layer_version__layer__name__icontains=search_term).exclude(pk__in=primary_results).exclude(pk__in=secondary_results)
+ secondary_results = machines.filter(
+ name__icontains=search_term).exclude(pk__in=primary_results)
+ tertiary_results = machines.filter(
+ layer_version__layer__name__icontains=search_term).exclude(
+ pk__in=primary_results).exclude(pk__in=secondary_results)
results = []
- for machine in list(primary_results) + list(secondary_results) + list(tertiary_results):
+ for machine in list(primary_results) + list(secondary_results) + \
+ list(tertiary_results):
detail = "[ %s ]" % (machine.layer_version.layer.name)
needed_fields = {
- 'id' : machine.pk,
- 'name' : machine.name,
- 'detail' : detail,
+ 'id': machine.pk,
+ 'name': machine.name,
+ 'detail': detail,
}
results.append(needed_fields)
-
return results
+
class RecipesTypeAhead(ToasterTypeAhead):
""" Typeahead for all the recipes available in the current project's
configuration """
- def __init__(self):
- super(RecipesTypeAhead, self).__init__()
-
def apply_search(self, search_term, prj, request):
recipes = prj.get_available_recipes()
recipes = recipes.order_by("name")
-
primary_results = recipes.filter(name__istartswith=search_term)
- secondary_results = recipes.filter(name__icontains=search_term).exclude(pk__in=primary_results)
- tertiary_results = recipes.filter(layer_version__layer__name__icontains=search_term).exclude(pk__in=primary_results).exclude(pk__in=secondary_results)
+ secondary_results = recipes.filter(
+ name__icontains=search_term).exclude(pk__in=primary_results)
+ tertiary_results = recipes.filter(
+ layer_version__layer__name__icontains=search_term).exclude(
+ pk__in=primary_results).exclude(pk__in=secondary_results)
results = []
- for recipe in list(primary_results) + list(secondary_results) + list(tertiary_results):
+ for recipe in list(primary_results) + list(secondary_results) + \
+ list(tertiary_results):
detail = "[ %s ]" % (recipe.layer_version.layer.name)
needed_fields = {
- 'id' : recipe.pk,
- 'name' : recipe.name,
- 'detail' : detail,
+ 'id': recipe.pk,
+ 'name': recipe.name,
+ 'detail': detail,
}
results.append(needed_fields)
return results
+
class ProjectsTypeAhead(ToasterTypeAhead):
""" Typeahead for all the projects, except for command line builds """
- def __init__(self):
- super(ProjectsTypeAhead, self).__init__()
-
def apply_search(self, search_term, prj, request):
projects = Project.objects.exclude(is_default=True).order_by("name")
primary_results = projects.filter(name__istartswith=search_term)
- secondary_results = projects.filter(name__icontains=search_term).exclude(pk__in=primary_results)
+ secondary_results = projects.filter(
+ name__icontains=search_term).exclude(pk__in=primary_results)
results = []
for project in list(primary_results) + list(secondary_results):
needed_fields = {
- 'id' : project.pk,
- 'name' : project.name,
- 'detail' : "",
- 'projectPageUrl' : reverse('project', args=(project.pk,))
+ 'id': project.pk,
+ 'name': project.name,
+ 'detail': "",
+ 'projectPageUrl': reverse('project', args=(project.pk,))
}
results.append(needed_fields)
return results
+
+
+class GitRevisionTypeAhead(ToasterTypeAhead):
+ def apply_search(self, search_term, prj, request):
+ results = []
+ git_url = request.GET.get('git_url')
+ ls_remote = cache.get(git_url)
+
+ if ls_remote is None:
+ ls_remote = subprocess.check_output(['git', 'ls-remote', git_url],
+ universal_newlines=True)
+ ls_remote = ls_remote.splitlines()
+ # Avoid fetching the list of git refs on each new input
+ cache.set(git_url, ls_remote, 120)
+
+ for rev in ls_remote:
+ git_rev = str(rev).split("/")[-1:][0]
+ # "HEAD" has a special meaning in Toaster... YOCTO #9924
+ if "HEAD" in git_rev:
+ continue
+
+ if git_rev.startswith(search_term):
+ results.append({'name': git_rev,
+ 'detail': '[ %s ]' % str(rev)})
+
+ return results
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/urls.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/urls.py
index ece9ac169..d92f190ae 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/urls.py
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/urls.py
@@ -182,20 +182,23 @@ urlpatterns = patterns('toastergui.views',
typeaheads.RecipesTypeAhead.as_view(), name='xhr_recipestypeahead'),
url(r'^xhr_typeahead/projects$',
typeaheads.ProjectsTypeAhead.as_view(), name='xhr_projectstypeahead'),
-
-
+ url(r'^xhr_typeahead/gitrev$',
+ typeaheads.GitRevisionTypeAhead.as_view(),
+ name='xhr_gitrevtypeahead'),
url(r'^xhr_testreleasechange/(?P<pid>\d+)$', 'xhr_testreleasechange',
name='xhr_testreleasechange'),
url(r'^xhr_configvaredit/(?P<pid>\d+)$', 'xhr_configvaredit',
name='xhr_configvaredit'),
- url(r'^xhr_importlayer/$', 'xhr_importlayer', name='xhr_importlayer'),
-
url(r'^xhr_layer/(?P<pid>\d+)/(?P<layerversion_id>\d+)$',
api.XhrLayer.as_view(),
name='xhr_layer'),
+ url(r'^xhr_layer/(?P<pid>\d+)$',
+ api.XhrLayer.as_view(),
+ name='xhr_layer'),
+
# JS Unit tests
url(r'^js-unit-tests/$', 'jsunittests', name='js-unit-tests'),
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/views.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/views.py
index 2efb0fd56..75c591103 100755
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/views.py
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/views.py
@@ -120,32 +120,6 @@ def objtojson(obj):
raise TypeError("Unserializable object %s (%s) of type %s" % ( obj, dir(obj), type(obj)))
-def _template_renderer(template):
- def func_wrapper(view):
- def returned_wrapper(request, *args, **kwargs):
- try:
- context = view(request, *args, **kwargs)
- except RedirectException as e:
- return e.get_redirect_response()
-
- if request.GET.get('format', None) == 'json':
- # objects is a special keyword - it's a Page, but we need the actual objects here
- # in XHR, the objects come in the "rows" property
- if "objects" in context:
- context["rows"] = context["objects"].object_list
- del context["objects"]
-
- # we're about to return; to keep up with the XHR API, we set the error to OK
- context["error"] = "ok"
-
- return HttpResponse(jsonfilter(context, default=objtojson ),
- content_type = "application/json; charset=utf-8")
- else:
- return render(request, template, context)
- return returned_wrapper
- return func_wrapper
-
-
def _lv_to_dict(prj, x = None):
if x is None:
def wrapper(x):
@@ -1509,121 +1483,6 @@ if True:
return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
- def xhr_importlayer(request):
- if ('vcs_url' not in request.POST or
- 'name' not in request.POST or
- 'git_ref' not in request.POST or
- 'project_id' not in request.POST):
- return HttpResponse(jsonfilter({"error": "Missing parameters; requires vcs_url, name, git_ref and project_id"}), content_type = "application/json")
-
- layers_added = [];
-
- # Rudimentary check for any possible html tags
- for val in request.POST.values():
- if "<" in val:
- return HttpResponse(jsonfilter(
- {"error": "Invalid character <"}),
- content_type="application/json")
-
- prj = Project.objects.get(pk=request.POST['project_id'])
-
- # Strip trailing/leading whitespace from all values
- # put into a new dict because POST one is immutable.
- post_data = dict()
- for key,val in request.POST.items():
- post_data[key] = val.strip()
-
-
- try:
- layer, layer_created = Layer.objects.get_or_create(name=post_data['name'])
- except MultipleObjectsReturned:
- return HttpResponse(jsonfilter({"error": "hint-layer-exists"}), content_type = "application/json")
-
- if layer:
- if layer_created:
- layer.vcs_url = post_data.get('vcs_url')
- layer.local_source_dir = post_data.get('local_source_dir')
- layer.up_date = timezone.now()
- layer.save()
- else:
- # We have an existing layer by this name, let's see if the git
- # url is the same, if it is then we can just create a new layer
- # version for this layer. Otherwise we need to bail out.
- if layer.vcs_url != post_data['vcs_url']:
- return HttpResponse(jsonfilter({"error": "hint-layer-exists-with-different-url" , "current_url" : layer.vcs_url, "current_id": layer.id }), content_type = "application/json")
-
- layer_version, version_created = \
- Layer_Version.objects.get_or_create(
- layer_source=LayerSource.TYPE_IMPORTED,
- layer=layer, project=prj,
- release=prj.release,
- branch=post_data['git_ref'],
- commit=post_data['git_ref'],
- dirpath=post_data['dir_path'])
-
- if layer_version:
- if not version_created:
- return HttpResponse(jsonfilter({"error":
- "hint-layer-version-exists",
- "existing_layer_version":
- layer_version.id }),
- content_type = "application/json")
-
- layer_version.layer_source = LayerSource.TYPE_IMPORTED
-
- layer_version.up_date = timezone.now()
- layer_version.save()
-
- # Add the dependencies specified for this new layer
- if ('layer_deps' in post_data and
- version_created and
- len(post_data["layer_deps"]) > 0):
- for layer_dep_id in post_data["layer_deps"].split(","):
-
- layer_dep_obj = Layer_Version.objects.get(pk=layer_dep_id)
- LayerVersionDependency.objects.get_or_create(layer_version=layer_version, depends_on=layer_dep_obj)
- # Now add them to the project, we could get an execption
- # if the project now contains the exact
- # dependency already (like modified on another page)
- try:
- prj_layer, prj_layer_created = ProjectLayer.objects.get_or_create(layercommit=layer_dep_obj, project=prj)
- except IntegrityError as e:
- logger.warning("Integrity error while saving Project Layers: %s (original %s)" % (e, e.__cause__))
- continue
-
- if prj_layer_created:
- layerdepdetailurl = reverse('layerdetails', args=(prj.id, layer_dep_obj.pk))
- layers_added.append({'id': layer_dep_obj.id, 'name': Layer.objects.get(id=layer_dep_obj.layer_id).name, 'layerdetailurl': layerdepdetailurl })
-
-
- # If an old layer version exists in our project then remove it
- for prj_layers in ProjectLayer.objects.filter(project=prj):
- dup_layer_v = Layer_Version.objects.filter(id=prj_layers.layercommit_id, layer_id=layer.id)
- if len(dup_layer_v) >0 :
- prj_layers.delete()
-
- # finally add the imported layer (version id) to the project
- ProjectLayer.objects.create(layercommit=layer_version, project=prj,optional=1)
-
- else:
- # We didn't create a layer version so back out now and clean up.
- if layer_created:
- layer.delete()
-
- return HttpResponse(jsonfilter({"error": "Uncaught error: Could not create layer version"}), content_type = "application/json")
-
- layerdetailurl = reverse('layerdetails', args=(prj.id, layer_version.pk))
-
- json_response = {"error": "ok",
- "imported_layer" : {
- "name" : layer.name,
- "id": layer_version.id,
- "layerdetailurl": layerdetailurl,
- },
- "deps_added": layers_added }
-
- return HttpResponse(jsonfilter(json_response), content_type = "application/json")
-
def customrecipe_download(request, pid, recipe_id):
recipe = get_object_or_404(CustomImageRecipe, pk=recipe_id)
@@ -1643,8 +1502,6 @@ if True:
}
return render(request, template, context)
- # TODO merge with api pseudo api here is used for deps modal
- @_template_renderer('layerdetails.html')
def layerdetails(request, pid, layerid):
project = Project.objects.get(pk=pid)
layer_version = Layer_Version.objects.get(pk=layerid)
@@ -1672,7 +1529,7 @@ if True:
'projectlayers': list(project_layers)
}
- return context
+ return render(request, 'layerdetails.html', context)
def get_project_configvars_context():
@@ -1692,7 +1549,6 @@ if True:
return(vars_managed,sorted(vars_fstypes),vars_blacklist)
- @_template_renderer("projectconf.html")
def projectconf(request, pid):
try:
@@ -1763,7 +1619,7 @@ if True:
except (ProjectVariable.DoesNotExist, BuildEnvironment.DoesNotExist):
pass
- return context
+ return render(request, "projectconf.html", context)
def _file_names_for_artifact(build, artifact_type, artifact_id):
"""
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/widgets.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/widgets.py
index 026903d35..6b7b981f3 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/widgets.py
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastergui/widgets.py
@@ -42,6 +42,8 @@ import json
import collections
import re
+from toastergui.tablefilter import TableFilterMap
+
try:
from urllib import unquote_plus
except ImportError:
@@ -50,8 +52,6 @@ except ImportError:
import logging
logger = logging.getLogger("toaster")
-from toastergui.tablefilter import TableFilterMap
-
class NoFieldOrDataName(Exception):
pass
@@ -259,9 +259,9 @@ class ToasterTable(TemplateView):
queries = query
if search_queries:
- search_queries &= queries
+ search_queries &= queries
else:
- search_queries = queries
+ search_queries = queries
self.queryset = self.queryset.filter(search_queries)
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/settings.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/settings.py
index aec9dbb92..1fd649c08 100644
--- a/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/settings.py
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/settings.py
@@ -38,8 +38,7 @@ ADMINS = (
MANAGERS = ADMINS
-TOASTER_SQLITE_DEFAULT_DIR = os.path.join(os.environ.get('TOASTER_DIR', ''),
- 'build')
+TOASTER_SQLITE_DEFAULT_DIR = os.environ.get('TOASTER_DIR')
DATABASES = {
'default': {
diff --git a/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/settings_production_example.py b/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/settings_production_example.py
new file mode 100644
index 000000000..61a288864
--- /dev/null
+++ b/import-layers/yocto-poky/bitbake/lib/toaster/toastermain/settings_production_example.py
@@ -0,0 +1,58 @@
+#
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake Toaster Implementation
+#
+# Copyright (C) 2016 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# See Django documentation for more information about deployment
+# https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
+
+# Toaster production settings example overlay
+# To use this copy this example to "settings_production.py" and set in your
+# environment DJANGO_SETTINGS_MODULE=toastermain.settings_production
+# This can be permanently set in a new .wsgi file
+
+from toastermain.settings import * # NOQA
+
+# Set this value!
+SECRET_KEY = None
+
+# Switch off any debugging
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.mysql',
+ 'NAME': 'toaster_data',
+ 'USER': 'toaster',
+ 'PASSWORD': 'yourpasswordhere',
+ 'HOST': '127.0.0.1',
+ 'PORT': '3306',
+ }
+}
+
+# Location where static files will be placed by "manage.py collectstatic"
+STATIC_ROOT = '/var/www/static-toaster/'
+
+# URL prefix for static files.
+STATIC_URL = '/static-toaster/'
+
+# Hosts that Django will serve
+# https://docs.djangoproject.com/en/1.8/ref/settings/#std:setting-ALLOWED_HOSTS
+ALLOWED_HOSTS = ['toaster-example.example.com']