summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexandru DAMIAN <alexandru.damian@intel.com>2015-05-13 13:21:33 +0100
committerRichard Purdie <richard.purdie@linuxfoundation.org>2015-05-14 18:04:10 +0100
commit80ca4f00f82cd2f58fe5cda38c8cc5a719c887b6 (patch)
treef2d5a130213c37b8aed1fb07fecf46eb9e5e1a13
parent23f5b009e0ebf59cfcc9ae90a1084468fc88e42f (diff)
downloadpoky-80ca4f00f82cd2f58fe5cda38c8cc5a719c887b6.tar.gz
bitbake: toaster/contrib: adding TTS squashed patch
In order to move the Toaster Test System in Toaster itself, we create a contrib directory. The TTS is added as a squashed patch with no history. It contains code contributed by Ke Zou <ke.zou@windriver.com>. (Bitbake rev: 7d24fea2b5dcaac6add738b6fb4700d698824286) Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r--bitbake/lib/toaster/contrib/README6
-rw-r--r--bitbake/lib/toaster/contrib/tts/README41
-rw-r--r--bitbake/lib/toaster/contrib/tts/TODO9
-rw-r--r--bitbake/lib/toaster/contrib/tts/backlog.txt2
-rw-r--r--bitbake/lib/toaster/contrib/tts/config.py70
-rwxr-xr-xbitbake/lib/toaster/contrib/tts/launcher.py100
-rw-r--r--bitbake/lib/toaster/contrib/tts/log/.create0
-rwxr-xr-xbitbake/lib/toaster/contrib/tts/recv.py51
-rwxr-xr-xbitbake/lib/toaster/contrib/tts/runner.py200
-rw-r--r--bitbake/lib/toaster/contrib/tts/scratchpad.py20
-rw-r--r--bitbake/lib/toaster/contrib/tts/settings.json5
-rw-r--r--bitbake/lib/toaster/contrib/tts/shellutils.py139
-rw-r--r--bitbake/lib/toaster/contrib/tts/tests.py57
-rwxr-xr-xbitbake/lib/toaster/contrib/tts/toasteruitest/run_toastertests.py87
-rwxr-xr-xbitbake/lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py1924
-rw-r--r--bitbake/lib/toaster/contrib/tts/toasteruitest/toaster_test.cfg21
-rw-r--r--bitbake/lib/toaster/contrib/tts/urlcheck.py44
-rw-r--r--bitbake/lib/toaster/contrib/tts/urllist.py53
18 files changed, 2829 insertions, 0 deletions
diff --git a/bitbake/lib/toaster/contrib/README b/bitbake/lib/toaster/contrib/README
new file mode 100644
index 0000000000..46d0ff008e
--- /dev/null
+++ b/bitbake/lib/toaster/contrib/README
@@ -0,0 +1,6 @@
1contrib directory for toaster
2
3This directory holds code that works with Toaster, without being an integral part of the Toaster project.
4It is intended for testing code, testing fixtures, tools for Toaster, etc.
5
6NOTE: This directory is NOT a Python module.
diff --git a/bitbake/lib/toaster/contrib/tts/README b/bitbake/lib/toaster/contrib/tts/README
new file mode 100644
index 0000000000..22fa5673ba
--- /dev/null
+++ b/bitbake/lib/toaster/contrib/tts/README
@@ -0,0 +1,41 @@
1
2Toaster Testing Framework
3Yocto Project
4
5
6Rationale
7------------
8As 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.
9
10The 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.
11
12
13Usage
14------------
15There are three main executable scripts in this directory.
16 * 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.
17 * 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
18 * 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.
19
20
21Installation
22------------
23As prerequisite, we expect a functioning email system on a machine with Python2.
24
25The broad steps to installation
26* set up the .forward on the receiving email account to pipe to the recv.py file
27* edit config.py and settings.json to alter for local installation settings
28* on email receive, verify backlog.txt to see that the tasks are received and marked for processing
29* execute launcher.py in command line to verify that a test occurs with no problems, and that the outgoing email is delivered
30* add launcher.py
31
32
33
34Contribute
35------------
36What we need are tests. Add your own tests to either tests.py file, or to a new file.
37Use "config.logger" to write logs that will make it to email.
38
39Commonly used code should be going to shellutils, and configuration to config.py.
40
41Contribute code by emailing patches to the list: toaster@yoctoproject.org (membership required)
diff --git a/bitbake/lib/toaster/contrib/tts/TODO b/bitbake/lib/toaster/contrib/tts/TODO
new file mode 100644
index 0000000000..117192106f
--- /dev/null
+++ b/bitbake/lib/toaster/contrib/tts/TODO
@@ -0,0 +1,9 @@
1We need to implement tests:
2
3automated link checker; currently
4$ linkchecker -t 1000 -F csv http://localhost:8000/
5
6integrate the w3c-validation service; currently
7$ python urlcheck.py
8
9
diff --git a/bitbake/lib/toaster/contrib/tts/backlog.txt b/bitbake/lib/toaster/contrib/tts/backlog.txt
new file mode 100644
index 0000000000..6f92f7ae6e
--- /dev/null
+++ b/bitbake/lib/toaster/contrib/tts/backlog.txt
@@ -0,0 +1,2 @@
1michaelw/toaster-frontend-cleanups|PENDING
2adamian/test_0|PENDING
diff --git a/bitbake/lib/toaster/contrib/tts/config.py b/bitbake/lib/toaster/contrib/tts/config.py
new file mode 100644
index 0000000000..a4ea8cf5fa
--- /dev/null
+++ b/bitbake/lib/toaster/contrib/tts/config.py
@@ -0,0 +1,70 @@
1#!/usr/bin/python
2
3# ex:ts=4:sw=4:sts=4:et
4# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
5#
6# Copyright (C) 2015 Alexandru Damian for Intel Corp.
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21# This is the configuration/single module for tts
22# everything that would be a global variable goes here
23
24import os, sys, logging
25
26LOGDIR = "log"
27SETTINGS_FILE = os.path.join(os.path.dirname(__file__), "settings.json")
28TEST_DIR_NAME = "tts_testdir"
29
30OWN_PID = os.getpid()
31
32OWN_EMAIL_ADDRESS = "Toaster Testing Framework <alexandru.damian@intel.com>"
33REPORT_EMAIL_ADDRESS = "alexandru.damian@intel.com"
34
35# make sure we have the basic logging infrastructure
36logger = logging.getLogger("toastertest")
37__console = logging.StreamHandler(sys.stdout)
38__console.setFormatter(logging.Formatter("%(asctime)s %(levelname)s: %(message)s"))
39logger.addHandler(__console)
40logger.setLevel(logging.DEBUG)
41
42
43# singleton file names
44LOCKFILE="/tmp/ttf.lock"
45BACKLOGFILE=os.path.join(os.path.dirname(__file__), "backlog.txt")
46
47# task states
48def enum(*sequential, **named):
49 enums = dict(zip(sequential, range(len(sequential))), **named)
50 reverse = dict((value, key) for key, value in enums.iteritems())
51 enums['reverse_mapping'] = reverse
52 return type('Enum', (), enums)
53
54
55class TASKS:
56 PENDING = "PENDING"
57 INPROGRESS = "INPROGRESS"
58 DONE = "DONE"
59
60 @staticmethod
61 def next_task(task):
62 if task == TASKS.PENDING:
63 return TASKS.INPROGRESS
64 if task == TASKS.INPROGRESS:
65 return TASKS.DONE
66 raise Exception("Invalid next task state for %s" % task)
67
68# TTS specific
69CONTRIB_REPO = "git@git.yoctoproject.org:poky-contrib"
70
diff --git a/bitbake/lib/toaster/contrib/tts/launcher.py b/bitbake/lib/toaster/contrib/tts/launcher.py
new file mode 100755
index 0000000000..5b63bc1de6
--- /dev/null
+++ b/bitbake/lib/toaster/contrib/tts/launcher.py
@@ -0,0 +1,100 @@
1#!/usr/bin/python
2
3# ex:ts=4:sw=4:sts=4:et
4# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
5#
6# Copyright (C) 2015 Alexandru Damian for Intel Corp.
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21# Program to run the next task listed from the backlog.txt; designed to be
22# run from crontab.
23
24from __future__ import print_function
25import sys, os, config, shellutils
26from shellutils import ShellCmdException
27
28# Import smtplib for the actual sending function
29import smtplib
30
31# Import the email modules we'll need
32from email.mime.text import MIMEText
33
34DEBUG=True
35
36def _take_lockfile():
37 return shellutils.lockfile(shellutils.mk_lock_filename())
38
39
40def read_next_task_by_state(task_state, task_name = None):
41 if not os.path.exists(os.path.join(os.path.dirname(__file__), config.BACKLOGFILE)):
42 return None
43 os.rename(config.BACKLOGFILE, config.BACKLOGFILE + ".tmp")
44 task = None
45 with open(config.BACKLOGFILE + ".tmp", "r") as f_in:
46 with open(config.BACKLOGFILE, "w") as f_out:
47 for line in f_in.readlines():
48 if task is None:
49 fields = line.strip().split("|", 2)
50 if fields[1] == task_state:
51 if task_name is None or task_name == fields[0]:
52 task = fields[0]
53 print("Updating %s %s to %s" % (task, task_state, config.TASKS.next_task(task_state)))
54 line = "%s|%s\n" % (task, config.TASKS.next_task(task_state))
55 f_out.write(line)
56 os.remove(config.BACKLOGFILE + ".tmp")
57 return task
58
59def send_report(task_name, plaintext, errtext = None):
60 if errtext is None:
61 msg = MIMEText(plaintext)
62 else:
63 if plaintext is None:
64 plaintext=""
65 msg = MIMEText("--STDOUT dump--\n\n%s\n\n--STDERR dump--\n\n%s" % (plaintext, errtext))
66
67 msg['Subject'] = "[review-request] %s - smoke test results" % task_name
68 msg['From'] = config.OWN_EMAIL_ADDRESS
69 msg['To'] = config.REPORT_EMAIL_ADDRESS
70
71 s = smtplib.SMTP("localhost")
72 s.sendmail(config.OWN_EMAIL_ADDRESS, [config.REPORT_EMAIL_ADDRESS], msg.as_string())
73 s.quit()
74
75if __name__ == "__main__":
76 # we don't do anything if we have another instance of us running
77 lf = _take_lockfile()
78
79 if lf is None:
80 if DEBUG:
81 print("Concurrent script in progress, exiting")
82 sys.exit(1)
83
84 next_task = read_next_task_by_state(config.TASKS.PENDING)
85 if next_task is not None:
86 print("Next task is", next_task)
87 errtext = None
88 out = None
89 try:
90 out = shellutils.run_shell_cmd("./runner.py %s" % next_task)
91 pass
92 except ShellCmdException as e:
93 print("Failed while running the test runner: %s", e)
94 errtext = e.__str__()
95 send_report(next_task, out, errtext)
96 read_next_task_by_state(config.TASKS.INPROGRESS, next_task)
97 else:
98 print("No task")
99
100 shellutils.unlockfile(lf)
diff --git a/bitbake/lib/toaster/contrib/tts/log/.create b/bitbake/lib/toaster/contrib/tts/log/.create
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/bitbake/lib/toaster/contrib/tts/log/.create
diff --git a/bitbake/lib/toaster/contrib/tts/recv.py b/bitbake/lib/toaster/contrib/tts/recv.py
new file mode 100755
index 0000000000..168294acab
--- /dev/null
+++ b/bitbake/lib/toaster/contrib/tts/recv.py
@@ -0,0 +1,51 @@
1#!/usr/bin/python
2
3# ex:ts=4:sw=4:sts=4:et
4# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
5#
6# Copyright (C) 2015 Alexandru Damian for Intel Corp.
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21# Program to receive review requests by email and log tasks to backlog.txt
22# Designed to be run by the email system from a .forward file:
23#
24# cat .forward
25# |[full/path]/recv.py
26
27from __future__ import print_function
28import sys, os, config, shellutils
29from shellutils import ShellCmdException
30
31from email.parser import Parser
32
33def recv_mail(datastring):
34 headers = Parser().parsestr(datastring)
35 return headers['subject']
36
37
38if __name__ == "__main__":
39 lf = shellutils.lockfile(shellutils.mk_lock_filename(), retry = True)
40
41 subject = recv_mail(sys.stdin.read())
42
43 subject_parts = subject.split()
44 if "[review-request]" in subject_parts:
45 task_name = subject_parts[subject_parts.index("[review-request]") + 1]
46 with open(os.path.join(os.path.dirname(__file__), config.BACKLOGFILE), "a") as fout:
47 line = "%s|%s\n" % (task_name, config.TASKS.PENDING)
48 fout.write(line)
49
50 shellutils.unlockfile(lf)
51
diff --git a/bitbake/lib/toaster/contrib/tts/runner.py b/bitbake/lib/toaster/contrib/tts/runner.py
new file mode 100755
index 0000000000..d2a6099bdb
--- /dev/null
+++ b/bitbake/lib/toaster/contrib/tts/runner.py
@@ -0,0 +1,200 @@
1#!/usr/bin/python
2
3# ex:ts=4:sw=4:sts=4:et
4# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
5#
6# Copyright (C) 2015 Alexandru Damian for Intel Corp.
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21
22# This is the main test execution controller. It is designed to be run
23# manually from the command line, or to be called from a different program
24# that schedules test execution.
25#
26# Execute runner.py -h for help.
27
28
29
30from __future__ import print_function
31import optparse
32import sys, os
33import unittest, inspect, importlib
34import logging, pprint, json
35
36from shellutils import *
37
38import config
39
40# we also log to a file, in addition to console, because our output is important
41__log_file_name =os.path.join(os.path.dirname(__file__),"log/tts_%d.log" % config.OWN_PID)
42mkdirhier(os.path.dirname(__log_file_name))
43__log_file = open(__log_file_name, "w")
44__file_handler = logging.StreamHandler(__log_file)
45__file_handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s: %(message)s"))
46
47config.logger.addHandler(__file_handler)
48
49
50# set up log directory
51try:
52 if not os.path.exists(config.LOGDIR):
53 os.mkdir(config.LOGDIR)
54 else:
55 if not os.path.isdir(config.LOGDIR):
56 raise Exception("Expected log dir '%s' is not actually a directory." % config.LOGDIR)
57except OSError as e:
58 raise e
59
60# creates the under-test-branch as a separate directory
61def set_up_test_branch(settings, branch_name):
62 testdir = "%s/%s.%d" % (settings['workdir'], config.TEST_DIR_NAME, config.OWN_PID)
63
64 # creates the host dir
65 if os.path.exists(testdir):
66 raise Exception("Test dir '%s'is already there, aborting" % testdir)
67 os.mkdir(testdir)
68
69 # copies over the .git from the localclone
70 run_shell_cmd("cp -a '%s'/.git '%s'" % (settings['localclone'], testdir))
71
72 # add the remote if it doesn't exist
73 crt_remotes = run_shell_cmd("git remote -v", cwd = testdir)
74 remotes = [word for line in crt_remotes.split("\n") for word in line.split()]
75 if not config.CONTRIB_REPO in remotes:
76 remote_name = "tts_contrib"
77 run_shell_cmd("git remote add %s %s" % (remote_name, config.CONTRIB_REPO), cwd = testdir)
78 else:
79 remote_name = remotes[remotes.index(config.CONTRIB_REPO) - 1]
80
81 # do the fetch
82 run_shell_cmd("git fetch %s -p" % remote_name, cwd=testdir)
83
84 # do the checkout
85 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)
86
87 return testdir
88
89
90def __search_for_tests():
91 # we find all classes that can run, and run them
92 tests = []
93 for dir_name, dirs_list, files_list in os.walk(os.path.dirname(os.path.abspath(__file__))):
94 for f in [f[:-3] for f in files_list if f.endswith(".py") and not f.startswith("__init__")]:
95 config.logger.debug("Inspecting module %s", f)
96 current_module = importlib.import_module(f)
97 crtclass_names = vars(current_module)
98 for v in crtclass_names:
99 t = crtclass_names[v]
100 if isinstance(t, type(unittest.TestCase)) and issubclass(t, unittest.TestCase):
101 tests.append((f,v))
102 break
103 return tests
104
105
106# boilerplate to self discover tests and run them
107def execute_tests(dir_under_test, testname):
108
109 if testname is not None and "." in testname:
110 tests = []
111 tests.append(tuple(testname.split(".", 2)))
112 else:
113 tests = __search_for_tests()
114
115 # let's move to the directory under test
116 crt_dir = os.getcwd()
117 os.chdir(dir_under_test)
118
119 # execute each module
120 try:
121 config.logger.debug("Discovered test clases: %s" % pprint.pformat(tests))
122 suite = unittest.TestSuite()
123 loader = unittest.TestLoader()
124 result = unittest.TestResult()
125 for m,t in tests:
126 suite.addTest(loader.loadTestsFromName("%s.%s" % (m,t)))
127 config.logger.info("Running %d test(s)", suite.countTestCases())
128 suite.run(result)
129
130 if len(result.errors) > 0:
131 map(lambda x: config.logger.error("Exception on test: %s" % pprint.pformat(x)), result.errors)
132
133 if len(result.failures) > 0:
134 map(lambda x: config.logger.error("Failed test: %s:\n%s\n" % (pprint.pformat(x[0]), "\n".join(["-- %s" % x for x in eval(pprint.pformat(x[1])).split("\n")]))), result.failures)
135
136 config.logger.info("Test results: %d ran, %d errors, %d failures" % (result.testsRun, len(result.errors), len(result.failures)))
137
138 except Exception as e:
139 import traceback
140 config.logger.error("Exception while running test. Tracedump: \n%s", traceback.format_exc(e))
141 finally:
142 os.chdir(crt_dir)
143 return len(result.failures)
144
145# verify that we had a branch-under-test name as parameter
146def validate_args():
147 from optparse import OptionParser
148 parser = OptionParser(usage="usage: %prog [options] branch_under_test")
149
150 parser.add_option("-t", "--test-dir", dest="testdir", default=None, help="Use specified directory to run tests, inhibits the checkout.")
151 parser.add_option("-s", "--single", dest="singletest", default=None, help="Run only the specified test")
152
153 (options, args) = parser.parse_args()
154 if len(args) < 1:
155 raise Exception("Please specify the branch to run on. Use option '-h' when in doubt.")
156 return (options, args)
157
158
159
160
161# load the configuration options
162def read_settings():
163 if not os.path.exists(config.SETTINGS_FILE) or not os.path.isfile(config.SETTINGS_FILE):
164 raise Exception("Config file '%s' cannot be openend" % config.SETTINGS_FILE);
165 return json.loads(open(config.SETTINGS_FILE, "r").read())
166
167
168# cleanup !
169def clean_up(testdir):
170 # TODO: delete the test dir
171 #run_shell_cmd("rm -rf -- '%s'" % testdir)
172 pass
173
174if __name__ == "__main__":
175 (options, args) = validate_args()
176
177 settings = read_settings()
178 need_cleanup = False
179
180 testdir = None
181 no_failures = 1
182 try:
183 if options.testdir is not None and os.path.exists(options.testdir):
184 testdir = options.testdir
185 config.logger.info("No checkout, using %s" % testdir)
186 else:
187 need_cleanup = True
188 testdir = set_up_test_branch(settings, args[0]) # we expect a branch name as first argument
189
190 config.testdir = testdir # we let tests know where to run
191 no_failures = execute_tests(testdir, options.singletest)
192
193 except ShellCmdException as e :
194 import traceback
195 config.logger.error("Error while setting up testing. Traceback: \n%s" % traceback.format_exc(e))
196 finally:
197 if need_cleanup and testdir is not None:
198 clean_up(testdir)
199
200 sys.exit(no_failures)
diff --git a/bitbake/lib/toaster/contrib/tts/scratchpad.py b/bitbake/lib/toaster/contrib/tts/scratchpad.py
new file mode 100644
index 0000000000..b276fb598b
--- /dev/null
+++ b/bitbake/lib/toaster/contrib/tts/scratchpad.py
@@ -0,0 +1,20 @@
1import config
2
3# Code testing section
4def _code_test():
5 def callback_writeeventlog(opt, opt_str, value, parser):
6 if len(parser.rargs) < 1 or parser.rargs[0].startswith("-"):
7 value = ""
8 else:
9 value = parser.rargs[0]
10 del parser.rargs[0]
11
12 setattr(parser.values, opt.dest, value)
13
14 parser = optparse.OptionParser()
15 parser.add_option("-w", "--write-log", help = "Writes the event log of the build to a bitbake event json file.",
16 action = "callback", callback=callback_writeeventlog, dest = "writeeventlog")
17
18 options, targets = parser.parse_args(sys.argv)
19
20 print (options, targets)
diff --git a/bitbake/lib/toaster/contrib/tts/settings.json b/bitbake/lib/toaster/contrib/tts/settings.json
new file mode 100644
index 0000000000..bb671eaf27
--- /dev/null
+++ b/bitbake/lib/toaster/contrib/tts/settings.json
@@ -0,0 +1,5 @@
1{
2 "repo": "git@git.yoctoproject.org:poky-contrib",
3 "localclone": "/home/ddalex/ssd/yocto/poky",
4 "workdir": "/home/ddalex/ssd/yocto"
5}
diff --git a/bitbake/lib/toaster/contrib/tts/shellutils.py b/bitbake/lib/toaster/contrib/tts/shellutils.py
new file mode 100644
index 0000000000..2b7f0f1d2e
--- /dev/null
+++ b/bitbake/lib/toaster/contrib/tts/shellutils.py
@@ -0,0 +1,139 @@
1#!/usr/bin/python
2
3# ex:ts=4:sw=4:sts=4:et
4# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
5#
6# Copyright (C) 2015 Alexandru Damian for Intel Corp.
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21# Utilities shared by tests and other common bits of code.
22
23import sys, os, subprocess, fcntl, errno
24import config
25from config import logger
26
27
28# License warning; this code is copied from the BitBake project, file bitbake/lib/bb/utils.py
29# The code is originally licensed GPL-2.0, and we redistribute it under still GPL-2.0
30
31# End of copy is marked with #ENDOFCOPY marker
32
33def mkdirhier(directory):
34 """Create a directory like 'mkdir -p', but does not complain if
35 directory already exists like os.makedirs
36 """
37
38 try:
39 os.makedirs(directory)
40 except OSError as e:
41 if e.errno != errno.EEXIST:
42 raise e
43
44def lockfile(name, shared=False, retry=True):
45 """
46 Use the file fn as a lock file, return when the lock has been acquired.
47 Returns a variable to pass to unlockfile().
48 """
49 config.logger.debug("take lockfile %s" % name)
50 dirname = os.path.dirname(name)
51 mkdirhier(dirname)
52
53 if not os.access(dirname, os.W_OK):
54 logger.error("Unable to acquire lock '%s', directory is not writable",
55 name)
56 sys.exit(1)
57
58 op = fcntl.LOCK_EX
59 if shared:
60 op = fcntl.LOCK_SH
61 if not retry:
62 op = op | fcntl.LOCK_NB
63
64 while True:
65 # If we leave the lockfiles lying around there is no problem
66 # but we should clean up after ourselves. This gives potential
67 # for races though. To work around this, when we acquire the lock
68 # we check the file we locked was still the lock file on disk.
69 # by comparing inode numbers. If they don't match or the lockfile
70 # no longer exists, we start again.
71
72 # This implementation is unfair since the last person to request the
73 # lock is the most likely to win it.
74
75 try:
76 lf = open(name, 'a+')
77 fileno = lf.fileno()
78 fcntl.flock(fileno, op)
79 statinfo = os.fstat(fileno)
80 if os.path.exists(lf.name):
81 statinfo2 = os.stat(lf.name)
82 if statinfo.st_ino == statinfo2.st_ino:
83 return lf
84 lf.close()
85 except Exception:
86 try:
87 lf.close()
88 except Exception:
89 pass
90 pass
91 if not retry:
92 return None
93
94def unlockfile(lf):
95 """
96 Unlock a file locked using lockfile()
97 """
98 try:
99 # If we had a shared lock, we need to promote to exclusive before
100 # removing the lockfile. Attempt this, ignore failures.
101 fcntl.flock(lf.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
102 os.unlink(lf.name)
103 except (IOError, OSError):
104 pass
105 fcntl.flock(lf.fileno(), fcntl.LOCK_UN)
106 lf.close()
107
108#ENDOFCOPY
109
110
111def mk_lock_filename():
112 our_name = os.path.basename(__file__)
113 our_name = ".%s" % ".".join(reversed(our_name.split(".")))
114 return config.LOCKFILE + our_name
115
116
117
118class ShellCmdException(Exception):
119 pass
120
121def run_shell_cmd(command, cwd = None):
122 if cwd is None:
123 cwd = os.getcwd()
124
125 config.logger.debug("_shellcmd: (%s) %s" % (cwd, command))
126 p = subprocess.Popen(command, cwd = cwd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
127 (out,err) = p.communicate()
128 p.wait()
129 if p.returncode:
130 if len(err) == 0:
131 err = "command: %s \n%s" % (command, out)
132 else:
133 err = "command: %s \n%s" % (command, err)
134 config.logger.warn("_shellcmd: error \n%s\n%s" % (out, err))
135 raise ShellCmdException(err)
136 else:
137 #config.logger.debug("localhostbecontroller: shellcmd success\n%s" % out)
138 return out
139
diff --git a/bitbake/lib/toaster/contrib/tts/tests.py b/bitbake/lib/toaster/contrib/tts/tests.py
new file mode 100644
index 0000000000..3d4cfea2b3
--- /dev/null
+++ b/bitbake/lib/toaster/contrib/tts/tests.py
@@ -0,0 +1,57 @@
1#!/usr/bin/python
2
3# ex:ts=4:sw=4:sts=4:et
4# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
5#
6# Copyright (C) 2015 Alexandru Damian for Intel Corp.
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21
22# Test definitions. The runner will look for and auto-discover the tests
23# no matter what they file are they in, as long as they are in the same directory
24# as this file.
25
26import unittest
27from shellutils import *
28
29class TestPyCompilable(unittest.TestCase):
30 ''' Verifies that all Python files are syntactically correct '''
31 def test_compile_file(self):
32 try:
33 out = run_shell_cmd("find . -name *py -type f -print0 | xargs -0 -n1 -P20 python -m py_compile", config.testdir)
34 except ShellCmdException as e:
35 self.fail("Error compiling python files: %s" % (e))
36 except Exception as e:
37 self.fail("Unknown error: %s" % e)
38
39
40class TestPySystemStart(unittest.TestCase):
41 ''' Attempts to start Toaster, verify that it is succesfull, and stop it '''
42 def setUp(self):
43 run_shell_cmd("bash -c 'rm -f build/*log'")
44
45 def test_start_interactive_mode(self):
46 try:
47 run_shell_cmd("bash -c 'source %s/oe-init-build-env && source toaster start && source toaster stop'" % config.testdir, config.testdir)
48 except ShellCmdException as e:
49 self.fail("Failed starting interactive mode: %s" % (e))
50
51 def test_start_managed_mode(self):
52 try:
53 run_shell_cmd("./poky/bitbake/bin/toaster webport=56789 & sleep 10 && curl http://localhost:56789/ && kill -2 %1")
54 pass
55 except ShellCmdException as e:
56 self.fail("Failed starting managed mode: %s" % (e))
57
diff --git a/bitbake/lib/toaster/contrib/tts/toasteruitest/run_toastertests.py b/bitbake/lib/toaster/contrib/tts/toasteruitest/run_toastertests.py
new file mode 100755
index 0000000000..880487cb6b
--- /dev/null
+++ b/bitbake/lib/toaster/contrib/tts/toasteruitest/run_toastertests.py
@@ -0,0 +1,87 @@
1#!/usr/bin/python
2
3# Copyright
4
5# DESCRIPTION
6# This is script for running all selected toaster cases on
7# selected web browsers manifested in toaster_test.cfg.
8
9# 1. How to start toaster in yocto:
10# $ source poky/oe-init-build-env
11# $ source toaster start
12# $ bitbake core-image-minimal
13
14# 2. How to install selenium on Ubuntu:
15# $ sudo apt-get install scrot python-pip
16# $ sudo pip install selenium
17
18# 3. How to install selenium addon in firefox:
19# Download the lastest firefox addon from http://release.seleniumhq.org/selenium-ide/
20# Then install it. You can also install firebug and firepath addon
21
22# 4. How to start writing a new case:
23# All you need to do is to implement the function test_xxx() and pile it on.
24
25# 5. How to test with Chrome browser
26# Download/install chrome on host
27# Download chromedriver from https://code.google.com/p/chromedriver/downloads/list according to your host type
28# put chromedriver in PATH, (e.g. /usr/bin/, bear in mind to chmod)
29# For windows host, you may put chromedriver.exe in the same directory as chrome.exe
30
31
32import unittest, time, re, sys, getopt, os, logging, platform
33import ConfigParser
34import subprocess
35
36
37class toaster_run_all():
38 def __init__(self):
39 # in case this script is called from other directory
40 os.chdir(os.path.abspath(sys.path[0]))
41 self.starttime = time.strptime(time.ctime())
42 self.parser = ConfigParser.SafeConfigParser()
43 found = self.parser.read('toaster_test.cfg')
44 self.host_os = platform.system().lower()
45 self.run_all_cases()
46 self.collect_log()
47
48 def get_test_cases(self):
49 # we have config groups for different os type in toaster_test.cfg
50 cases_to_run = eval(self.parser.get('toaster_test_' + self.host_os, 'test_cases'))
51 return cases_to_run
52
53
54 def run_all_cases(self):
55 cases_temp = self.get_test_cases()
56 for case in cases_temp:
57 single_case_cmd = "python -m unittest toaster_automation_test.toaster_cases.test_" + str(case)
58 print single_case_cmd
59 subprocess.call(single_case_cmd, shell=True)
60
61 def collect_log(self):
62 """
63 the log files are temporarily stored in ./log/tmp/..
64 After all cases are done, they should be transfered to ./log/$TIMESTAMP/
65 """
66 def comple(number):
67 if number < 10:
68 return str(0) + str(number)
69 else:
70 return str(number)
71 now = self.starttime
72 now_str = comple(now.tm_year) + comple(now.tm_mon) + comple(now.tm_mday) + \
73 comple(now.tm_hour) + comple(now.tm_min) + comple(now.tm_sec)
74 log_dir = os.path.abspath(sys.path[0]) + os.sep + 'log' + os.sep + now_str
75 log_tmp_dir = os.path.abspath(sys.path[0]) + os.sep + 'log' + os.sep + 'tmp'
76 try:
77 os.renames(log_tmp_dir, log_dir)
78 except OSError :
79 logging.error(" Cannot create log dir(timestamp) under log, please check your privilege")
80
81
82if __name__ == "__main__":
83 toaster_run_all()
84
85
86
87
diff --git a/bitbake/lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py b/bitbake/lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py
new file mode 100755
index 0000000000..c03a937b89
--- /dev/null
+++ b/bitbake/lib/toaster/contrib/tts/toasteruitest/toaster_automation_test.py
@@ -0,0 +1,1924 @@
1#!/usr/bin/python
2# Copyright
3
4# DESCRIPTION
5# This is toaster automation base class and test cases file
6
7# History:
8# 2015.03.09 inital version
9# 2015.03.23 adding toaster_test.cfg, run_toastertest.py so we can run case by case from outside
10
11# Briefs:
12# This file is comprised of 3 parts:
13# I: common utils like sorting, getting attribute.. etc
14# II: base class part, which complies with unittest frame work and
15# contains class selenium-based functions
16# III: test cases
17# to add new case: just implement new test_xxx() function in class toaster_cases
18
19# NOTES for cases:
20# case 946:
21# step 6 - 8 needs to be observed using screenshots
22# case 956:
23# step 2 - 3 needs to be run manually
24
25import unittest, time, re, sys, getopt, os, logging, string, errno, exceptions
26import shutil, argparse, ConfigParser, platform
27from selenium import webdriver
28from selenium.common.exceptions import NoSuchElementException
29from selenium import selenium
30from selenium.webdriver.common.by import By
31from selenium.webdriver.common.keys import Keys
32from selenium.webdriver.support.ui import Select
33
34
35###########################################
36# #
37# PART I: utils stuff #
38# #
39###########################################
40
41class Listattr(object):
42 """
43 Set of list attribute. This is used to determine what the list content is.
44 Later on we may add more attributes here.
45 """
46 NULL = "null"
47 NUMBERS = "numbers"
48 STRINGS = "strings"
49 PERCENT = "percentage"
50 SIZE = "size"
51 UNKNOWN = "unknown"
52
53
54def get_log_root_dir():
55 max_depth = 5
56 parent_dir = '../'
57 for number in range(0, max_depth):
58 if os.path.isdir(sys.path[0] + os.sep + (os.pardir + os.sep)*number + 'log'):
59 log_root_dir = os.path.abspath(sys.path[0] + os.sep + (os.pardir + os.sep)*number + 'log')
60 break
61
62 if number == (max_depth - 1):
63 print 'No log dir found. Please check'
64 raise Exception
65
66 return log_root_dir
67
68
69def mkdir_p(dir):
70 try:
71 os.makedirs(dir)
72 except OSError as exc:
73 if exc.errno == errno.EEXIST and os.path.isdir(dir):
74 pass
75 else:
76 raise
77
78
79def get_list_attr(testlist):
80 """
81 To determine the list content
82 """
83 if not testlist:
84 return Listattr.NULL
85 listtest = testlist[:]
86 try:
87 listtest.remove('')
88 except ValueError:
89 pass
90 pattern_percent = re.compile(r"^([0-9])+(\.)?([0-9])*%$")
91 pattern_size = re.compile(r"^([0-9])+(\.)?([0-9])*( )*(K)*(M)*(G)*B$")
92 pattern_number = re.compile(r"^([0-9])+(\.)?([0-9])*$")
93 def get_patterned_number(pattern, tlist):
94 count = 0
95 for item in tlist:
96 if re.search(pattern, item):
97 count += 1
98 return count
99 if get_patterned_number(pattern_percent, listtest) == len(listtest):
100 return Listattr.PERCENT
101 elif get_patterned_number(pattern_size, listtest) == len(listtest):
102 return Listattr.SIZE
103 elif get_patterned_number(pattern_number, listtest) == len(listtest):
104 return Listattr.NUMBERS
105 else:
106 return Listattr.STRINGS
107
108
109def is_list_sequenced(testlist):
110 """
111 Function to tell if list is sequenced
112 Currently we may have list made up of: Strings ; numbers ; percentage ; time; size
113 Each has respective way to determine if it's sequenced.
114 """
115 test_list = testlist[:]
116 try:
117 test_list.remove('')
118 except ValueError:
119 pass
120
121 if get_list_attr(testlist) == Listattr.NULL :
122 return True
123
124 elif get_list_attr(testlist) == Listattr.STRINGS :
125 return (sorted(test_list) == test_list)
126
127 elif get_list_attr(testlist) == Listattr.NUMBERS :
128 list_number = []
129 for item in test_list:
130 list_number.append(eval(item))
131 return (sorted(list_number) == list_number)
132
133 elif get_list_attr(testlist) == Listattr.PERCENT :
134 list_number = []
135 for item in test_list:
136 list_number.append(eval(item.strip('%')))
137 return (sorted(list_number) == list_number)
138
139 elif get_list_attr(testlist) == Listattr.SIZE :
140 list_number = []
141 # currently SIZE is splitted by space
142 for item in test_list:
143 if item.split()[1].upper() == "KB":
144 list_number.append(1024 * eval(item.split()[0]))
145 elif item.split()[1].upper() == "MB":
146 list_number.append(1024 * 1024 * eval(item.split()[0]))
147 elif item.split()[1].upper() == "GB":
148 list_number.append(1024 * 1024 * 1024 * eval(item.split()[0]))
149 else:
150 list_number.append(eval(item.split()[0]))
151 return (sorted(list_number) == list_number)
152
153 else:
154 print 'Unrecognized list type, please check'
155 return False
156
157
158def is_list_inverted(testlist):
159 """
160 Function to tell if list is inverted
161 Currently we may have list made up of: Strings ; numbers ; percentage ; time; size
162 Each has respective way to determine if it's inverted.
163 """
164 test_list = testlist[:]
165 try:
166 test_list.remove('')
167 except ValueError:
168 pass
169
170 if get_list_attr(testlist) == Listattr.NULL :
171 return True
172
173 elif get_list_attr(testlist) == Listattr.STRINGS :
174 return (sorted(test_list, reverse = True) == test_list)
175
176 elif get_list_attr(testlist) == Listattr.NUMBERS :
177 list_number = []
178 for item in test_list:
179 list_number.append(eval(item))
180 return (sorted(list_number, reverse = True) == list_number)
181
182 elif get_list_attr(testlist) == Listattr.PERCENT :
183 list_number = []
184 for item in test_list:
185 list_number.append(eval(item.strip('%')))
186 return (sorted(list_number, reverse = True) == list_number)
187
188 elif get_list_attr(testlist) == Listattr.SIZE :
189 list_number = []
190 # currently SIZE is splitted by space. such as 0 B; 1 KB; 2 MB
191 for item in test_list:
192 if item.split()[1].upper() == "KB":
193 list_number.append(1024 * eval(item.split()[0]))
194 elif item.split()[1].upper() == "MB":
195 list_number.append(1024 * 1024 * eval(item.split()[0]))
196 elif item.split()[1].upper() == "GB":
197 list_number.append(1024 * 1024 * 1024 * eval(item.split()[0]))
198 else:
199 list_number.append(eval(item.split()[0]))
200 return (sorted(list_number, reverse = True) == list_number)
201
202 else:
203 print 'Unrecognized list type, please check'
204 return False
205
206def replace_file_content(filename, item, option):
207 f = open(filename)
208 lines = f.readlines()
209 f.close()
210 output = open(filename, 'w')
211 for line in lines:
212 if line.startswith(item):
213 output.write(item + " = '" + option + "'\n")
214 else:
215 output.write(line)
216 output.close()
217
218def extract_number_from_string(s):
219 """
220 extract the numbers in a string. return type is 'list'
221 """
222 return re.findall(r'([0-9]+)', s)
223
224
225
226###########################################
227# #
228# PART II: base class #
229# #
230###########################################
231
232class toaster_cases_base(unittest.TestCase):
233
234 def setUp(self):
235 self.screenshot_sequence = 1
236 self.verificationErrors = []
237 self.accept_next_alert = True
238 self.host_os = platform.system().lower()
239 self.parser = ConfigParser.SafeConfigParser()
240 configs = self.parser.read('toaster_test.cfg')
241 self.base_url = eval(self.parser.get('toaster_test_' + self.host_os, 'toaster_url'))
242
243 # create log dir . Currently , we put log files in log/tmp. After all
244 # test cases are done, move them to log/$datetime dir
245 self.log_tmp_dir = os.path.abspath(sys.path[0]) + os.sep + 'log' + os.sep + 'tmp'
246 try:
247 mkdir_p(self.log_tmp_dir)
248 except OSError :
249 logging.error("%(asctime)s Cannot create tmp dir under log, please check your privilege")
250 self.log = self.logger_create()
251 # driver setup
252 self.setup_browser()
253
254 def logger_create(self):
255 """
256 we use root logger for every testcase.
257 The reason why we don't use TOASTERXXX_logger is to avoid setting respective level for
258 root logger and TOASTERXXX_logger
259 To Be Discussed
260 """
261 log_level_dict = {'CRITICAL':logging.CRITICAL, 'ERROR':logging.ERROR, 'WARNING':logging.WARNING, \
262 'INFO':logging.INFO, 'DEBUG':logging.DEBUG, 'NOTSET':logging.NOTSET}
263 log = logging.getLogger()
264# log = logging.getLogger('TOASTER_' + str(self.case_no))
265 self.logging_level = eval(self.parser.get('toaster_test_' + self.host_os, 'logging_level'))
266 key = self.logging_level.upper()
267 log.setLevel(log_level_dict[key])
268 fh = logging.FileHandler(filename=self.log_tmp_dir + os.sep + 'case_all' + '.log', mode='a')
269 ch = logging.StreamHandler(sys.stdout)
270 formatter = logging.Formatter('%(pathname)s - %(lineno)d - %(asctime)s \n \
271 %(name)s - %(levelname)s - %(message)s')
272 fh.setFormatter(formatter)
273 ch.setFormatter(formatter)
274 log.addHandler(fh)
275 log.addHandler(ch)
276 return log
277
278
279 def setup_browser(self, *browser_path):
280 self.browser = eval(self.parser.get('toaster_test_' + self.host_os, 'test_browser'))
281 print self.browser
282 if self.browser == "firefox":
283 driver = webdriver.Firefox()
284 elif self.browser == "chrome":
285 driver = webdriver.Chrome()
286 elif self.browser == "ie":
287 driver = webdriver.Ie()
288 else:
289 driver = None
290 print "unrecognized browser type, please check"
291 self.driver = driver
292 self.driver.implicitly_wait(30)
293 return self.driver
294
295
296 def save_screenshot(self, **log_args):
297 """
298 This function is used to save screen either by os interface or selenium interface.
299 How to use:
300 self.save_screenshot(screenshot_type = 'native'/'selenium', log_sub_dir = 'xxx',
301 append_name = 'stepx')
302 where native means screenshot func provided by OS,
303 selenium means screenshot func provided by selenium webdriver
304 """
305 types = [log_args.get('screenshot_type')]
306 # when no screenshot_type is specified
307 if types == [None]:
308 types = ['native', 'selenium']
309 # normally append_name is used to specify which step..
310 add_name = log_args.get('append_name')
311 if not add_name:
312 add_name = '-'
313 # normally there's no need to specify sub_dir
314 sub_dir = log_args.get('log_sub_dir')
315 if not sub_dir:
316 # use casexxx as sub_dir name
317 sub_dir = 'case' + str(self.case_no)
318 for item in types:
319 log_dir = self.log_tmp_dir + os.sep + sub_dir
320 mkdir_p(log_dir)
321 log_path = log_dir + os.sep + self.browser + '-' +\
322 item + '-' + add_name + '-' + str(self.screenshot_sequence) + '.png'
323 if item == 'native':
324 os.system("scrot " + log_path)
325 elif item == 'selenium':
326 self.driver.get_screenshot_as_file(log_path)
327 self.screenshot_sequence += 1
328
329 def browser_delay(self):
330 """
331 currently this is a workaround for some chrome test.
332 Sometimes we need a delay to accomplish some operation.
333 But for firefox, mostly we don't need this.
334 To be discussed
335 """
336 if self.browser == "chrome":
337 time.sleep(1)
338 return
339
340
341# these functions are not contained in WebDriver class..
342 def find_element_by_text(self, string):
343 return self.driver.find_element_by_xpath("//*[text()='" + string + "']")
344
345
346 def find_elements_by_text(self, string):
347 return self.driver.find_elements_by_xpath("//*[text()='" + string + "']")
348
349
350 def find_element_by_text_in_table(self, table_id, text_string):
351 """
352 This is used to search some certain 'text' in certain table
353 """
354 try:
355 table_element = self.get_table_element(table_id)
356 element = table_element.find_element_by_xpath("//*[text()='" + text_string + "']")
357 except NoSuchElementException, e:
358 print 'no element found'
359 raise
360 return element
361
362
363 def find_element_by_link_text_in_table(self, table_id, link_text):
364 """
365 Assume there're multiple suitable "find_element_by_link_text".
366 In this circumstance we need to specify "table".
367 """
368 try:
369 table_element = self.get_table_element(table_id)
370 element = table_element.find_element_by_link_text(link_text)
371 except NoSuchElementException, e:
372 print 'no element found'
373 raise
374 return element
375
376
377 def find_elements_by_link_text_in_table(self, table_id, link_text):
378 """
379 Search link-text in certain table. This helps to narrow down search area.
380 """
381 try:
382 table_element = self.get_table_element(table_id)
383 element_list = table_element.find_elements_by_link_text(link_text)
384 except NoSuchElementException, e:
385 print 'no element found'
386 raise
387 return element_list
388
389
390 def find_element_by_partial_link_text_in_table(self, table_id, link_text):
391 """
392 Search element by partial link text in certain table.
393 """
394 try:
395 table_element = self.get_table_element(table_id)
396 element = table_element.find_element_by_partial_link_text(link_text)
397 return element
398 except NoSuchElementException, e:
399 print 'no element found'
400 raise
401
402
403 def find_elements_by_partial_link_text_in_table(self, table_id, link_text):
404 """
405 Assume there're multiple suitable "find_partial_element_by_link_text".
406 """
407 try:
408 table_element = self.get_table_element(table_id)
409 element_list = table_element.find_elements_by_partial_link_text(link_text)
410 return element_list
411 except NoSuchElementException, e:
412 print 'no element found'
413 raise
414
415
416 def find_element_by_xpath_in_table(self, table_id, xpath):
417 """
418 This helps to narrow down search area. Especially useful when dealing with pop-up form.
419 """
420 try:
421 table_element = self.get_table_element(table_id)
422 element = table_element.find_element_by_xpath(xpath)
423 except NoSuchElementException, e:
424 print 'no element found'
425 raise
426 return element
427
428
429 def find_elements_by_xpath_in_table(self, table_id, xpath):
430 """
431 This helps to narrow down search area. Especially useful when dealing with pop-up form.
432 """
433 try:
434 table_element = self.get_table_element(table_id)
435 element_list = table_element.find_elements_by_xpath(xpath)
436 except NoSuchElementException, e:
437 print 'no elements found'
438 raise
439 return element_list
440
441
442 def shortest_xpath(self, pname, pvalue):
443 return "//*[@" + pname + "='" + pvalue + "']"
444
445
446#usually elements in the same column are with same class name. for instance: class="outcome" .TBD
447 def get_table_column_text(self, attr_name, attr_value):
448 c_xpath = self.shortest_xpath(attr_name, attr_value)
449 elements = self.driver.find_elements_by_xpath(c_xpath)
450 c_list = []
451 for element in elements:
452 c_list.append(element.text)
453 return c_list
454
455
456 def get_table_column_text_by_column_number(self, table_id, column_number):
457 c_xpath = "//*[@id='" + table_id + "']//td[" + str(column_number) + "]"
458 elements = self.driver.find_elements_by_xpath(c_xpath)
459 c_list = []
460 for element in elements:
461 c_list.append(element.text)
462 return c_list
463
464
465 def get_table_head_text(self, *table_id):
466#now table_id is a tuple...
467 if table_id:
468 thead_xpath = "//*[@id='" + table_id[0] + "']//thead//th[text()]"
469 elements = self.driver.find_elements_by_xpath(thead_xpath)
470 c_list = []
471 for element in elements:
472 if element.text:
473 c_list.append(element.text)
474 return c_list
475#default table on page
476 else:
477 return self.driver.find_element_by_xpath("//*/table/thead").text
478
479
480
481 def get_table_element(self, table_id, *coordinate):
482 if len(coordinate) == 0:
483#return whole-table element
484 element_xpath = "//*[@id='" + table_id + "']"
485 try:
486 element = self.driver.find_element_by_xpath(element_xpath)
487 except NoSuchElementException, e:
488 raise
489 return element
490 row = coordinate[0]
491
492 if len(coordinate) == 1:
493#return whole-row element
494 element_xpath = "//*[@id='" + table_id + "']/tbody/tr[" + str(row) + "]"
495 try:
496 element = self.driver.find_element_by_xpath(element_xpath)
497 except NoSuchElementException, e:
498 return False
499 return element
500#now we are looking for an element with specified X and Y
501 column = coordinate[1]
502
503 element_xpath = "//*[@id='" + table_id + "']/tbody/tr[" + str(row) + "]/td[" + str(column) + "]"
504 try:
505 element = self.driver.find_element_by_xpath(element_xpath)
506 except NoSuchElementException, e:
507 return False
508 return element
509
510
511 def get_table_data(self, table_id, row_count, column_count):
512 row = 1
513 Lists = []
514 while row <= row_count:
515 column = 1
516 row_content=[]
517 while column <= column_count:
518 s= "//*[@id='" + table_id + "']/tbody/tr[" + str(row) +"]/td[" + str(column) + "]"
519 v = self.driver.find_element_by_xpath(s).text
520 row_content.append(v)
521 column = column + 1
522 print("row_content=",row_content)
523 Lists.extend(row_content)
524 print Lists[row-1][0]
525 row = row + 1
526 return Lists
527
528 # The is_xxx_present functions only returns True/False
529 # All the log work is done in test procedure, so we can easily trace back
530 # using logging
531 def is_text_present (self, patterns):
532 for pattern in patterns:
533 if str(pattern) not in self.driver.page_source:
534 return False
535 return True
536
537
538 def is_element_present(self, how, what):
539 try:
540 self.driver.find_element(how, what)
541 except NoSuchElementException, e:
542 return False
543 return True
544
545
546 def is_alert_present(self):
547 try: self.driver.switch_to_alert()
548 except NoAlertPresentException, e: return False
549 return True
550
551
552 def close_alert_and_get_its_text(self):
553 try:
554 alert = self.driver.switch_to_alert()
555 alert_text = alert.text
556 if self.accept_next_alert:
557 alert.accept()
558 else:
559 alert.dismiss()
560 return alert_text
561 finally: self.accept_next_alert = True
562
563
564 def get_case_number(self):
565 """
566 what case are we running now
567 """
568 funcname = sys._getframe(1).f_code.co_name
569 caseno_str = funcname.strip('test_')
570 try:
571 caseno = int(caseno_str)
572 except ValueError:
573 print "get case number error! please check if func name is test_xxx"
574 return False
575 return caseno
576
577
578 def tearDown(self):
579 self.log.info(' END: CASE %s log \n\n' % str(self.case_no))
580 self.driver.quit()
581 self.assertEqual([], self.verificationErrors)
582
583
584###################################################################
585# #
586# PART III: test cases #
587# please refer to #
588# https://bugzilla.yoctoproject.org/tr_show_case.cgi?case_id=xxx #
589# #
590###################################################################
591
592# Note: to comply with the unittest framework, we call these test_xxx functions
593# from run_toastercases.py to avoid calling setUp() and tearDown() multiple times
594
595
596class toaster_cases(toaster_cases_base):
597 ##############
598 # CASE 901 #
599 ##############
600 def test_901(self):
601 # the reason why get_case_number is not in setUp function is that
602 # otherwise it returns "setUp" instead of "test_xxx"
603 self.case_no = self.get_case_number()
604 self.log.info(' CASE %s log: ' % str(self.case_no))
605 self.driver.maximize_window()
606 self.driver.get(self.base_url)
607 # open all columns
608 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
609 # adding explicitly wait for chromedriver..-_-
610 self.browser_delay()
611 self.driver.find_element_by_id("log").click()
612 self.browser_delay()
613 self.driver.find_element_by_id("started_on").click()
614 self.browser_delay()
615 self.driver.find_element_by_id("time").click()
616 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
617 # dict: {lint text name : actual class name}
618 table_head_dict = {'Outcome':'outcome', 'Target':'target', 'Machine':'machine', 'Started on':'started_on', 'Completed on':'completed_on', \
619 'Errors':'errors_no', 'Warnings':'warnings_no', 'Time':'time', 'Log':'log'}
620 for key in table_head_dict:
621 try:
622 self.driver.find_element_by_link_text(key).click()
623 except Exception, e:
624 self.log.error("%s cannot be found on page" % key)
625 raise
626 column_list = self.get_table_column_text("class", table_head_dict[key])
627 # after 1st click, the list should be either sequenced or inverted, but we don't have a "default order" here
628 # the point is, after another click, it should be another order
629 if is_list_inverted(column_list):
630 self.driver.find_element_by_link_text(key).click()
631 column_list = self.get_table_column_text("class", table_head_dict[key])
632 self.failUnless(is_list_sequenced(column_list))
633 else:
634 self.failUnless(is_list_sequenced(column_list))
635 self.driver.find_element_by_link_text(key).click()
636 column_list = self.get_table_column_text("class", table_head_dict[key])
637 self.failUnless(is_list_inverted(column_list))
638 self.log.info("case passed")
639
640
641 ##############
642 # CASE 902 #
643 ##############
644 def test_902(self):
645 self.case_no = self.get_case_number()
646 self.log.info(' CASE %s log: ' % str(self.case_no))
647 self.driver.maximize_window()
648 self.driver.get(self.base_url)
649 # Could add more test patterns here in the future. Also, could search some items other than target column in future..
650 patterns = ["minimal", "sato"]
651 for pattern in patterns:
652 ori_target_column_texts = self.get_table_column_text("class", "target")
653 print ori_target_column_texts
654 self.driver.find_element_by_id("search").clear()
655 self.driver.find_element_by_id("search").send_keys(pattern)
656 self.driver.find_element_by_css_selector("button.btn").click()
657 new_target_column_texts = self.get_table_column_text("class", "target")
658 # if nothing found, we still count it as "pass"
659 if new_target_column_texts:
660 for text in new_target_column_texts:
661 self.failUnless(text.find(pattern))
662 self.driver.find_element_by_css_selector("i.icon-remove").click()
663 target_column_texts = self.get_table_column_text("class", "target")
664 self.failUnless(ori_target_column_texts == target_column_texts)
665
666
667 ##############
668 # CASE 903 #
669 ##############
670 def test_903(self):
671 self.case_no = self.get_case_number()
672 self.log.info(' CASE %s log: ' % str(self.case_no))
673 self.driver.maximize_window()
674 self.driver.get(self.base_url)
675 # when opening a new page, "started_on" is not displayed by default
676 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
677 # currently all the delay are for chrome driver -_-
678 self.browser_delay()
679 self.driver.find_element_by_id("started_on").click()
680 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
681 # step 4
682 items = ["Outcome", "Completed on", "Started on", "Failed tasks", "Errors", "Warnings"]
683 for item in items:
684 try:
685 temp_element = self.find_element_by_text_in_table('otable', item)
686 # this is how we find "filter icon" in the same level as temp_element(where "a" means clickable, "i" means icon)
687 self.failUnless(temp_element.find_element_by_xpath("..//*/a/i[@class='icon-filter filtered']"))
688 except Exception,e:
689 self.log.error(" %s cannot be found! %s" % (item, e))
690 self.failIf(True)
691 raise
692 # step 5-6
693 temp_element = self.find_element_by_link_text_in_table('otable', 'Outcome')
694 temp_element.find_element_by_xpath("..//*/a/i[@class='icon-filter filtered']").click()
695 self.browser_delay()
696 # the 2nd option, whatever it is
697 self.driver.find_element_by_xpath("(//input[@name='filter'])[2]").click()
698 # click "Apply" button
699 self.driver.find_element_by_xpath("//*[@id='filter_outcome']//*[text()='Apply']").click()
700 # save screen here
701 time.sleep(1)
702 self.save_screenshot(screenshot_type='selenium', append_name='step5')
703 temp_element = self.find_element_by_link_text_in_table('otable', 'Completed on')
704 temp_element.find_element_by_xpath("..//*/a/i[@class='icon-filter filtered']").click()
705 self.browser_delay()
706 self.driver.find_element_by_xpath("//*[@id='filter_completed_on']//*[text()='Apply']").click()
707 # save screen here to compare to previous one
708 # please note that for chrome driver, need a little break before saving
709 # screen here -_-
710 self.browser_delay()
711 self.save_screenshot(screenshot_type='selenium', append_name='step6')
712 self.driver.find_element_by_id("search").clear()
713 self.driver.find_element_by_id("search").send_keys("core-image")
714 self.driver.find_element_by_css_selector("button.btn").click()
715
716
717 ##############
718 # CASE 904 #
719 ##############
720 def test_904(self):
721 self.case_no = self.get_case_number()
722 self.log.info(' CASE %s log: ' % str(self.case_no))
723 self.driver.maximize_window()
724 self.driver.get(self.base_url)
725 self.driver.find_element_by_partial_link_text("core-image").click()
726 self.driver.find_element_by_link_text("Tasks").click()
727# self.driver.find_element_by_link_text("All builds").click()
728# self.driver.back()
729 self.table_name = 'otable'
730 # This is how we find the "default" rows-number!
731 rows_displayed = int(Select(self.driver.find_element_by_css_selector("select.pagesize")).first_selected_option.text)
732 print rows_displayed
733 self.failUnless(self.get_table_element(self.table_name, rows_displayed))
734 self.failIf(self.get_table_element(self.table_name, rows_displayed + 1))
735 # Search text box background text is "Search tasks"
736 self.failUnless(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search tasks']"))
737
738 self.driver.find_element_by_id("search").clear()
739 self.driver.find_element_by_id("search").send_keys("busybox")
740 self.driver.find_element_by_css_selector("button.btn").click()
741 self.browser_delay()
742 self.save_screenshot(screenshot_type='selenium', append_name='step5')
743 self.driver.find_element_by_css_selector("i.icon-remove").click()
744 # Save screen here
745 self.save_screenshot(screenshot_type='selenium', append_name='step5_2')
746 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
747 self.driver.find_element_by_id("cpu_used").click()
748 self.driver.find_element_by_id("disk_io").click()
749 self.driver.find_element_by_id("task_log").click()
750 self.driver.find_element_by_id("recipe_version").click()
751 self.driver.find_element_by_id("time_taken").click()
752 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
753 # The operation is the same as case901
754 # dict: {lint text name : actual class name}
755 table_head_dict = {'Order':'order', 'Recipe':'recipe_name', 'Task':'task_name', 'Executed':'executed', \
756 'Outcome':'outcome', 'Cache attempt':'cache_attempt', 'Time (secs)':'time_taken', 'CPU usage':'cpu_used', \
757 'Disk I/O (ms)':'disk_io', 'Log':'task_log'}
758 for key in table_head_dict:
759# This is tricky here: we are doing so because there may be more than 1
760# same-name link_text in one page. So we only find element inside the table
761 self.find_element_by_link_text_in_table(self.table_name, key).click()
762 column_list = self.get_table_column_text("class", table_head_dict[key])
763# after 1st click, the list should be either sequenced or inverted, but we don't have a "default order" here
764# the point is, after another click, it should be another order
765# the fist case is special:this means every item in column_list is the same, so
766# after one click, either sequenced or inverted will be fine
767 if (is_list_inverted(column_list) and is_list_sequenced(column_list)) \
768 or (not column_list) :
769 self.find_element_by_link_text_in_table(self.table_name, key).click()
770 column_list = self.get_table_column_text("class", table_head_dict[key])
771 self.failUnless(is_list_sequenced(column_list) or is_list_inverted(column_list))
772 elif is_list_inverted(column_list):
773 self.find_element_by_link_text_in_table(self.table_name, key).click()
774 column_list = self.get_table_column_text("class", table_head_dict[key])
775 self.failUnless(is_list_sequenced(column_list))
776 else:
777 self.failUnless(is_list_sequenced(column_list))
778 self.find_element_by_link_text_in_table(self.table_name, key).click()
779 column_list = self.get_table_column_text("class", table_head_dict[key])
780 self.failUnless(is_list_inverted(column_list))
781# step 8-10
782 # filter dict: {link text name : filter table name in xpath}
783 filter_dict = {'Executed':'filter_executed', 'Outcome':'filter_outcome', 'Cache attempt':'filter_cache_attempt'}
784 for key in filter_dict:
785 temp_element = self.find_element_by_link_text_in_table(self.table_name, key)
786 # find the filter icon besides it.
787 # And here we must have break (1 sec) to get the popup stuff
788 temp_element.find_element_by_xpath("..//*[@class='icon-filter filtered']").click()
789 self.browser_delay()
790 avail_options = self.driver.find_elements_by_xpath("//*[@id='" + filter_dict[key] + "']//*[@name='filter'][not(@disabled)]")
791 for number in range(0, len(avail_options)):
792 avail_options[number].click()
793 self.browser_delay()
794 # click "Apply"
795 self.driver.find_element_by_xpath("//*[@id='" + filter_dict[key] + "']//*[@type='submit']").click()
796 # insert screen capture here
797 self.browser_delay()
798 self.save_screenshot(screenshot_type='selenium', append_name='step8')
799 # after the last option was clicked, we don't need operation below anymore
800 if number < len(avail_options)-1:
801 temp_element = self.find_element_by_link_text_in_table(self.table_name, key)
802 temp_element.find_element_by_xpath("..//*[@class='icon-filter filtered']").click()
803 avail_options = self.driver.find_elements_by_xpath("//*[@id='" + filter_dict[key] + "']//*[@name='filter'][not(@disabled)]")
804 self.browser_delay()
805# step 11
806 for item in ['order', 'task_name', 'executed', 'outcome', 'recipe_name', 'recipe_version']:
807 try:
808 self.find_element_by_xpath_in_table(self.table_name, "./tbody/tr[1]/*[@class='" + item + "']/a").click()
809 except NoSuchElementException, e:
810 # let it go...
811 print 'no item in the colum' + item
812 # insert screen shot here
813 self.save_screenshot(screenshot_type='selenium', append_name='step11')
814 self.driver.back()
815# step 12-14
816 # about test_dict: please refer to testcase 904 requirement step 12-14
817 test_dict = {
818 'Time':{
819 'class':'time_taken',
820 'check_head_list':['Recipe', 'Task', 'Executed', 'Outcome', 'Time (secs)'],
821 'check_column_list':['cpu_used', 'cache_attempt', 'disk_io', 'task_log', 'order', 'recipe_version']
822 },
823 'CPU usage':{
824 'class':'cpu_used',
825 'check_head_list':['Recipe', 'Task', 'Executed', 'Outcome', 'CPU usage'],
826 'check_column_list':['cache_attempt', 'disk_io', 'task_log', 'order', 'recipe_version', 'time_taken']
827 },
828 'Disk I/O':{
829 'class':'disk_io',
830 'check_head_list':['Recipe', 'Task', 'Executed', 'Outcome', 'Disk I/O (ms)'],
831 'check_column_list':['cpu_used', 'cache_attempt', 'task_log', 'order', 'recipe_version', 'time_taken']
832 }
833 }
834 for key in test_dict:
835 self.find_element_by_partial_link_text_in_table('nav', 'core-image').click()
836 self.find_element_by_link_text_in_table('nav', key).click()
837 head_list = self.get_table_head_text('otable')
838 for item in test_dict[key]['check_head_list']:
839 self.failUnless(item in head_list)
840 column_list = self.get_table_column_text('class', test_dict[key]['class'])
841 self.failUnless(is_list_inverted(column_list))
842
843 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
844 for item2 in test_dict[key]['check_column_list']:
845 self.driver.find_element_by_id(item2).click()
846 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
847 # TBD: save screen here
848
849
850 ##############
851 # CASE 906 #
852 ##############
853 def test_906(self):
854 self.case_no = self.get_case_number()
855 self.log.info(' CASE %s log: ' % str(self.case_no))
856 self.driver.maximize_window()
857 self.driver.get(self.base_url)
858 self.driver.find_element_by_link_text("core-image-minimal").click()
859 self.find_element_by_link_text_in_table('nav', 'Packages').click()
860 # find "bash" in first column (Packages)
861 self.driver.find_element_by_xpath("//*[@id='otable']//td[1]//*[text()='bash']").click()
862 # save sceen here to observe...
863# step 6
864 self.driver.find_element_by_partial_link_text("Generated files").click()
865 head_list = self.get_table_head_text('otable')
866 for item in ['File', 'Size']:
867 self.failUnless(item in head_list)
868 c_list = self.get_table_column_text('class', 'path')
869 self.failUnless(is_list_sequenced(c_list))
870# step 7
871 self.driver.find_element_by_partial_link_text("Runtime dependencies").click()
872 # save sceen here to observe...
873 # note that here table name is not 'otable'
874 head_list = self.get_table_head_text('dependencies')
875 for item in ['Package', 'Version', 'Size']:
876 self.failUnless(item in head_list)
877 c_list = self.get_table_column_text_by_column_number('dependencies', 1)
878 self.failUnless(is_list_sequenced(c_list))
879 texts = ['Size', 'License', 'Recipe', 'Recipe version', 'Layer', \
880 'Layer branch', 'Layer commit', 'Layer directory']
881 self.failUnless(self.is_text_present(texts))
882
883
884 ##############
885 # CASE 910 #
886 ##############
887 def test_910(self):
888 self.case_no = self.get_case_number()
889 self.log.info(' CASE %s log: ' % str(self.case_no))
890 image_type="core-image-minimal"
891 test_package1="busybox"
892 test_package2="lib"
893 self.driver.maximize_window()
894 self.driver.get(self.base_url)
895 self.driver.find_element_by_link_text(image_type).click()
896 self.driver.find_element_by_link_text("Recipes").click()
897 self.save_screenshot(screenshot_type='selenium', append_name='step3')
898
899 self.table_name = 'otable'
900 # This is how we find the "default" rows-number!
901 rows_displayed = int(Select(self.driver.find_element_by_css_selector("select.pagesize")).first_selected_option.text)
902 print rows_displayed
903 self.failUnless(self.get_table_element(self.table_name, rows_displayed))
904 self.failIf(self.get_table_element(self.table_name, rows_displayed + 1))
905
906 # Check the default table is sorted by Recipe
907 tasks_column_count = len(self.driver.find_elements_by_xpath("/html/body/div[2]/div/div[2]/div[2]/table/tbody/tr/td[1]"))
908 print tasks_column_count
909 default_column_list = self.get_table_column_text_by_column_number(self.table_name, 1)
910 #print default_column_list
911
912 self.failUnless(is_list_sequenced(default_column_list))
913
914 # Search text box background text is "Search recipes"
915 self.failUnless(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search recipes']"))
916
917 self.driver.find_element_by_id("search").clear()
918 self.driver.find_element_by_id("search").send_keys(test_package1)
919 self.driver.find_element_by_css_selector("button.btn").click()
920 # Save screen here
921 self.save_screenshot(screenshot_type='selenium', append_name='step4')
922 self.driver.find_element_by_css_selector("i.icon-remove").click()
923 self.save_screenshot(screenshot_type='selenium', append_name='step4_2')
924
925 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
926 self.driver.find_element_by_id("depends_on").click()
927 self.driver.find_element_by_id("layer_version__branch").click()
928 self.driver.find_element_by_id("layer_version__layer__commit").click()
929 self.driver.find_element_by_id("layer_version__layer__local_path").click()
930 self.driver.find_element_by_id("depends_by").click()
931 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
932
933 self.find_element_by_link_text_in_table(self.table_name, 'Recipe').click()
934 # Check the inverted table by Recipe
935 # Recipe doesn't have class name
936 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]"))
937 print inverted_tasks_column_count
938 inverted_column_list = self.get_table_column_text_by_column_number(self.table_name, 1)
939 #print inverted_column_list
940
941 self.driver.find_element_by_xpath("/html/body/div[2]/div/div[2]/div[2]/table/tbody/tr[1]/td[1]/a").click()
942 self.driver.back()
943 self.failUnless(is_list_inverted(inverted_column_list))
944 self.find_element_by_link_text_in_table(self.table_name, 'Recipe').click()
945
946 table_head_dict = {'Recipe file':'recipe_file', 'Section':'recipe_section', \
947 'License':'recipe_license', 'Layer':'layer_version__layer__name', \
948 'Layer branch':'layer_version__branch', 'Layer directory':'layer_version__layer__local_path'}
949 for key in table_head_dict:
950 self.find_element_by_link_text_in_table(self.table_name, key).click()
951 column_list = self.get_table_column_text("class", table_head_dict[key])
952 if (is_list_inverted(column_list) and is_list_sequenced(column_list)) \
953 or (not column_list) :
954 self.find_element_by_link_text_in_table(self.table_name, key).click()
955 column_list = self.get_table_column_text("class", table_head_dict[key])
956 self.failUnless(is_list_sequenced(column_list) or is_list_inverted(column_list))
957 self.driver.find_element_by_xpath("/html/body/div[2]/div/div[2]/div[2]/table/tbody/tr[1]/td[1]/a").click()
958 self.driver.back()
959 self.failUnless(is_list_sequenced(column_list) or is_list_inverted(column_list))
960 # Search text box background text is "Search recipes"
961 self.failUnless(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search recipes']"))
962 self.driver.find_element_by_id("search").clear()
963 self.driver.find_element_by_id("search").send_keys(test_package2)
964 self.driver.find_element_by_css_selector("button.btn").click()
965 column_search_list = self.get_table_column_text("class", table_head_dict[key])
966 self.failUnless(is_list_sequenced(column_search_list) or is_list_inverted(column_search_list))
967 self.driver.find_element_by_css_selector("i.icon-remove").click()
968 elif is_list_inverted(column_list):
969 self.find_element_by_link_text_in_table(self.table_name, key).click()
970 column_list = self.get_table_column_text("class", table_head_dict[key])
971 self.failUnless(is_list_sequenced(column_list))
972 self.driver.find_element_by_xpath("/html/body/div[2]/div/div[2]/div[2]/table/tbody/tr[1]/td[1]/a").click()
973 self.driver.back()
974 self.failUnless(is_list_sequenced(column_list))
975 # Search text box background text is "Search recipes"
976 self.failUnless(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search recipes']"))
977 self.driver.find_element_by_id("search").clear()
978 self.driver.find_element_by_id("search").send_keys(test_package2)
979 self.driver.find_element_by_css_selector("button.btn").click()
980 column_search_list = self.get_table_column_text("class", table_head_dict[key])
981 self.failUnless(is_list_sequenced(column_search_list))
982 self.driver.find_element_by_css_selector("i.icon-remove").click()
983 else:
984 self.failUnless(is_list_sequenced(column_list))
985 self.find_element_by_link_text_in_table(self.table_name, key).click()
986 column_list = self.get_table_column_text("class", table_head_dict[key])
987 self.failUnless(is_list_inverted(column_list))
988 self.driver.find_element_by_xpath("/html/body/div[2]/div/div[2]/div[2]/table/tbody/tr[1]/td[1]/a").click()
989 self.driver.back()
990 self.failUnless(is_list_inverted(column_list))
991 # Search text box background text is "Search recipes"
992 self.failUnless(self.driver.find_element_by_xpath("//*[@id='searchform']/*[@placeholder='Search recipes']"))
993 self.driver.find_element_by_id("search").clear()
994 self.driver.find_element_by_id("search").send_keys(test_package2)
995 self.driver.find_element_by_css_selector("button.btn").click()
996 column_search_list = self.get_table_column_text("class", table_head_dict[key])
997 #print column_search_list
998 self.failUnless(is_list_inverted(column_search_list))
999 self.driver.find_element_by_css_selector("i.icon-remove").click()
1000
1001 # Bug 5919
1002 for key in table_head_dict:
1003 print key
1004 self.find_element_by_link_text_in_table(self.table_name, key).click()
1005 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1006 self.driver.find_element_by_id(table_head_dict[key]).click()
1007 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1008 self.browser_delay()
1009 # After hide the column, the default table should be sorted by Recipe
1010 tasks_column_count = len(self.driver.find_elements_by_xpath("/html/body/div[2]/div/div[2]/div[2]/table/tbody/tr/td[1]"))
1011 #print tasks_column_count
1012 default_column_list = self.get_table_column_text_by_column_number(self.table_name, 1)
1013 #print default_column_list
1014 self.failUnless(is_list_sequenced(default_column_list))
1015
1016 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1017 self.driver.find_element_by_id("recipe_file").click()
1018 self.driver.find_element_by_id("recipe_section").click()
1019 self.driver.find_element_by_id("recipe_license").click()
1020 self.driver.find_element_by_id("layer_version__layer__name").click()
1021 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1022
1023
1024 ##############
1025 # CASE 911 #
1026 ##############
1027 def test_911(self):
1028 self.case_no = self.get_case_number()
1029 self.log.info(' CASE %s log: ' % str(self.case_no))
1030 self.driver.maximize_window()
1031 self.driver.get(self.base_url)
1032 self.driver.find_element_by_link_text("core-image-minimal").click()
1033 self.find_element_by_link_text_in_table('nav', 'Recipes').click()
1034# step 3-5
1035 self.driver.find_element_by_id("search").clear()
1036 self.driver.find_element_by_id("search").send_keys("lib")
1037 self.driver.find_element_by_css_selector("button.btn").click()
1038 # save screen here for observation
1039 self.save_screenshot(screenshot_type='selenium', append_name='step5')
1040# step 6
1041 self.driver.find_element_by_css_selector("i.icon-remove").click()
1042 self.driver.find_element_by_id("search").clear()
1043 # we deliberately want "no result" here
1044 self.driver.find_element_by_id("search").send_keys("what the hell")
1045 self.driver.find_element_by_css_selector("button.btn").click()
1046 self.find_element_by_text("Show all recipes").click()
1047 self.driver.quit()
1048
1049
1050 ##############
1051 # CASE 912 #
1052 ##############
1053 def test_912(self):
1054 self.case_no = self.get_case_number()
1055 self.log.info(' CASE %s log: ' % str(self.case_no))
1056 self.driver = self.setup_browser(self)
1057 self.driver.maximize_window()
1058 self.driver.get(self.base_url)
1059 self.driver.find_element_by_link_text("core-image-minimal").click()
1060 self.find_element_by_link_text_in_table('nav', 'Recipes').click()
1061 # step 3
1062 head_list = self.get_table_head_text('otable')
1063 for item in ['Recipe', 'Recipe version', 'Recipe file', 'Section', 'License', 'Layer']:
1064 self.failUnless(item in head_list)
1065 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1066 self.driver.find_element_by_id("depends_on").click()
1067 self.driver.find_element_by_id("layer_version__branch").click()
1068 self.driver.find_element_by_id("layer_version__layer__commit").click()
1069 self.driver.find_element_by_id("layer_version__layer__local_path").click()
1070 self.driver.find_element_by_id("depends_by").click()
1071 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1072 # check if columns selected above is shown
1073 check_list = ['Dependencies', 'Layer branch', 'Layer commit', 'Layer directory', 'Reverse dependencies']
1074 head_list = self.get_table_head_text('otable')
1075 time.sleep(2)
1076 print head_list
1077 for item in check_list:
1078 self.failUnless(item in head_list)
1079 # un-check 'em all
1080 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1081 self.driver.find_element_by_id("depends_on").click()
1082 self.driver.find_element_by_id("layer_version__branch").click()
1083 self.driver.find_element_by_id("layer_version__layer__commit").click()
1084 self.driver.find_element_by_id("layer_version__layer__local_path").click()
1085 self.driver.find_element_by_id("depends_by").click()
1086 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1087 # don't exist any more
1088 head_list = self.get_table_head_text('otable')
1089 for item in check_list:
1090 self.failIf(item in head_list)
1091
1092
1093 ##############
1094 # CASE 913 #
1095 ##############
1096 def test_913(self):
1097 self.case_no = self.get_case_number()
1098 self.log.info(' CASE %s log: ' % str(self.case_no))
1099 self.driver.maximize_window()
1100 self.driver.get(self.base_url)
1101 self.driver.find_element_by_link_text("core-image-minimal").click()
1102 self.find_element_by_link_text_in_table('nav', 'Recipes').click()
1103 # step 3
1104 head_list = self.get_table_head_text('otable')
1105 for item in ['Recipe', 'Recipe version', 'Recipe file', 'Section', 'License', 'Layer']:
1106 self.failUnless(item in head_list)
1107 # step 4
1108 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1109 # save screen
1110 self.browser_delay()
1111 self.save_screenshot(screenshot_type='selenium', append_name='step4')
1112 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1113
1114
1115 ##############
1116 # CASE 914 #
1117 ##############
1118 def test_914(self):
1119 self.case_no = self.get_case_number()
1120 self.log.info(' CASE %s log: ' % str(self.case_no))
1121 image_type="core-image-minimal"
1122 test_package1="busybox"
1123 test_package2="gdbm"
1124 test_package3="gettext-native"
1125 driver = self.driver
1126 driver.maximize_window()
1127 driver.get(self.base_url)
1128 driver.find_element_by_link_text(image_type).click()
1129 driver.find_element_by_link_text("Recipes").click()
1130 driver.find_element_by_link_text(test_package1).click()
1131
1132 self.table_name = 'information'
1133
1134 tasks_row_count = len(driver.find_elements_by_xpath("/html/body/div[2]/div/div[3]/div/div[1]/table/tbody/tr/td[1]"))
1135 tasks_column_count = len(driver.find_elements_by_xpath("/html/body/div[2]/div/div[3]/div/div[1]/table/tbody/tr[1]/td"))
1136 print tasks_row_count
1137 print tasks_column_count
1138
1139 Tasks_column = self.get_table_column_text_by_column_number(self.table_name, 2)
1140 print ("Tasks_column=", Tasks_column)
1141
1142 key_tasks=["do_fetch", "do_unpack", "do_patch", "do_configure", "do_compile", "do_install", "do_package", "do_build"]
1143 i = 0
1144 while i < len(key_tasks):
1145 if key_tasks[i] not in Tasks_column:
1146 print ("Error! Missing key task: %s" % key_tasks[i])
1147 else:
1148 print ("%s is in tasks" % key_tasks[i])
1149 i = i + 1
1150
1151 if Tasks_column.index(key_tasks[0]) != 0:
1152 print ("Error! %s is not in the right position" % key_tasks[0])
1153 else:
1154 print ("%s is in right position" % key_tasks[0])
1155
1156 if Tasks_column[-1] != key_tasks[-1]:
1157 print ("Error! %s is not in the right position" % key_tasks[-1])
1158 else:
1159 print ("%s is in right position" % key_tasks[-1])
1160
1161 driver.find_element_by_partial_link_text("Packages (").click()
1162 packages_name = driver.find_element_by_partial_link_text("Packages (").text
1163 print packages_name
1164 packages_num = string.atoi(filter(str.isdigit, repr(packages_name)))
1165 print packages_num
1166
1167 packages_row_count = len(driver.find_elements_by_xpath("/html/body/div[2]/div/div[3]/div/div[2]/table/tbody/tr/td[1]"))
1168 print packages_row_count
1169
1170 if packages_num != packages_row_count:
1171 print ("Error! The packages number is not correct")
1172 else:
1173 print ("The pakcages number is correct")
1174
1175 driver.find_element_by_partial_link_text("Build dependencies (").click()
1176 depends_name = driver.find_element_by_partial_link_text("Build dependencies (").text
1177 print depends_name
1178 depends_num = string.atoi(filter(str.isdigit, repr(depends_name)))
1179 print depends_num
1180
1181 if depends_num == 0:
1182 depends_message = repr(driver.find_element_by_css_selector("div.alert.alert-info").text)
1183 print depends_message
1184 if depends_message.find("has no build dependencies.") < 0:
1185 print ("Error! The message isn't expected.")
1186 else:
1187 print ("The message is expected")
1188 else:
1189 depends_row_count = len(driver.find_elements_by_xpath("/html/body/div[2]/div/div[3]/div/div[3]/table/tbody/tr/td[1]"))
1190 print depends_row_count
1191 if depends_num != depends_row_count:
1192 print ("Error! The dependent packages number is not correct")
1193 else:
1194 print ("The dependent packages number is correct")
1195
1196 driver.find_element_by_partial_link_text("Reverse build dependencies (").click()
1197 rdepends_name = driver.find_element_by_partial_link_text("Reverse build dependencies (").text
1198 print rdepends_name
1199 rdepends_num = string.atoi(filter(str.isdigit, repr(rdepends_name)))
1200 print rdepends_num
1201
1202 if rdepends_num == 0:
1203 rdepends_message = repr(driver.find_element_by_css_selector("#brought-in-by > div.alert.alert-info").text)
1204 print rdepends_message
1205 if rdepends_message.find("has no reverse build dependencies.") < 0:
1206 print ("Error! The message isn't expected.")
1207 else:
1208 print ("The message is expected")
1209 else:
1210 print ("The reverse dependent packages number is correct")
1211
1212 driver.find_element_by_link_text("Recipes").click()
1213 driver.find_element_by_link_text(test_package2).click()
1214 driver.find_element_by_partial_link_text("Packages (").click()
1215 driver.find_element_by_partial_link_text("Build dependencies (").click()
1216 driver.find_element_by_partial_link_text("Reverse build dependencies (").click()
1217
1218
1219 driver.find_element_by_link_text("Recipes").click()
1220 driver.find_element_by_link_text(test_package3).click()
1221
1222 native_tasks_row_count = len(driver.find_elements_by_xpath("/html/body/div[2]/div/div[3]/div/div[1]/table/tbody/tr/td[1]"))
1223 native_tasks_column_count = len(driver.find_elements_by_xpath("/html/body/div[2]/div/div[3]/div/div[1]/table/tbody/tr[1]/td"))
1224 print native_tasks_row_count
1225 print native_tasks_column_count
1226
1227 Native_Tasks_column = self.get_table_column_text_by_column_number(self.table_name, 2)
1228 print ("Native_Tasks_column=", Native_Tasks_column)
1229
1230 native_key_tasks=["do_fetch", "do_unpack", "do_patch", "do_configure", "do_compile", "do_install", "do_build"]
1231 i = 0
1232 while i < len(native_key_tasks):
1233 if native_key_tasks[i] not in Native_Tasks_column:
1234 print ("Error! Missing key task: %s" % native_key_tasks[i])
1235 else:
1236 print ("%s is in tasks" % native_key_tasks[i])
1237 i = i + 1
1238
1239 if Native_Tasks_column.index(native_key_tasks[0]) != 0:
1240 print ("Error! %s is not in the right position" % native_key_tasks[0])
1241 else:
1242 print ("%s is in right position" % native_key_tasks[0])
1243
1244 if Native_Tasks_column[-1] != native_key_tasks[-1]:
1245 print ("Error! %s is not in the right position" % native_key_tasks[-1])
1246 else:
1247 print ("%s is in right position" % native_key_tasks[-1])
1248
1249 driver.find_element_by_partial_link_text("Packages (").click()
1250 native_packages_name = driver.find_element_by_partial_link_text("Packages (").text
1251 print native_packages_name
1252 native_packages_num = string.atoi(filter(str.isdigit, repr(native_packages_name)))
1253 print native_packages_num
1254
1255 if native_packages_num != 0:
1256 print ("Error! Native task shouldn't have any packages.")
1257 else:
1258 native_package_message = repr(driver.find_element_by_css_selector("div.alert.alert-info").text)
1259 print native_package_message
1260 if native_package_message.find("does not build any packages.") < 0:
1261 print ("Error! The message for native task isn't expected.")
1262 else:
1263 print ("The message for native task is expected.")
1264
1265 driver.find_element_by_partial_link_text("Build dependencies (").click()
1266 native_depends_name = driver.find_element_by_partial_link_text("Build dependencies (").text
1267 print native_depends_name
1268 native_depends_num = string.atoi(filter(str.isdigit, repr(native_depends_name)))
1269 print native_depends_num
1270
1271 native_depends_row_count = len(driver.find_elements_by_xpath("/html/body/div[2]/div/div[3]/div/div[3]/table/tbody/tr/td[1]"))
1272 print native_depends_row_count
1273
1274 if native_depends_num != native_depends_row_count:
1275 print ("Error! The dependent packages number is not correct")
1276 else:
1277 print ("The dependent packages number is correct")
1278
1279 driver.find_element_by_partial_link_text("Reverse build dependencies (").click()
1280 native_rdepends_name = driver.find_element_by_partial_link_text("Reverse build dependencies (").text
1281 print native_rdepends_name
1282 native_rdepends_num = string.atoi(filter(str.isdigit, repr(native_rdepends_name)))
1283 print native_rdepends_num
1284
1285 native_rdepends_row_count = len(driver.find_elements_by_xpath("/html/body/div[2]/div/div[3]/div/div[4]/table/tbody/tr/td[1]"))
1286 print native_rdepends_row_count
1287
1288 if native_rdepends_num != native_rdepends_row_count:
1289 print ("Error! The reverse dependent packages number is not correct")
1290 else:
1291 print ("The reverse dependent packages number is correct")
1292
1293 driver.find_element_by_link_text("Recipes").click()
1294
1295
1296 ##############
1297 # CASE 915 #
1298 ##############
1299 def test_915(self):
1300 self.case_no = self.get_case_number()
1301 self.log.info(' CASE %s log: ' % str(self.case_no))
1302 self.driver.maximize_window()
1303 self.driver.get(self.base_url)
1304 self.driver.find_element_by_link_text("core-image-minimal").click()
1305# step 3
1306 self.find_element_by_link_text_in_table('nav', 'Configuration').click()
1307 self.driver.find_element_by_link_text("BitBake variables").click()
1308# step 4
1309 self.driver.find_element_by_id("search").clear()
1310 self.driver.find_element_by_id("search").send_keys("lib")
1311 self.driver.find_element_by_css_selector("button.btn").click()
1312 # save screen to see result
1313 self.browser_delay()
1314 self.save_screenshot(screenshot_type='selenium', append_name='step4')
1315# step 5
1316 self.driver.find_element_by_css_selector("i.icon-remove").click()
1317 head_list = self.get_table_head_text('otable')
1318 print head_list
1319 print len(head_list)
1320 self.failUnless(head_list == ['Variable', 'Value', 'Set in file', 'Description'])
1321# step 8
1322 # search other string. and click "Variable" to re-sort, check if table
1323 # head is still the same
1324 self.driver.find_element_by_id("search").clear()
1325 self.driver.find_element_by_id("search").send_keys("poky")
1326 self.driver.find_element_by_css_selector("button.btn").click()
1327 self.find_element_by_link_text_in_table('otable', 'Variable').click()
1328 head_list = self.get_table_head_text('otable')
1329 self.failUnless(head_list == ['Variable', 'Value', 'Set in file', 'Description'])
1330 self.find_element_by_link_text_in_table('otable', 'Variable').click()
1331 head_list = self.get_table_head_text('otable')
1332 self.failUnless(head_list == ['Variable', 'Value', 'Set in file', 'Description'])
1333
1334
1335 ##############
1336 # CASE 916 #
1337 ##############
1338 def test_916(self):
1339 self.case_no = self.get_case_number()
1340 self.log.info(' CASE %s log: ' % str(self.case_no))
1341 self.driver.maximize_window()
1342 self.driver.get(self.base_url)
1343 self.driver.find_element_by_link_text("core-image-minimal").click()
1344# step 2-3
1345 self.find_element_by_link_text_in_table('nav', 'Configuration').click()
1346 self.driver.find_element_by_link_text("BitBake variables").click()
1347 variable_list = self.get_table_column_text('class', 'variable_name')
1348 self.failUnless(is_list_sequenced(variable_list))
1349# step 4
1350 self.find_element_by_link_text_in_table('otable', 'Variable').click()
1351 variable_list = self.get_table_column_text('class', 'variable_name')
1352 self.failUnless(is_list_inverted(variable_list))
1353 self.find_element_by_link_text_in_table('otable', 'Variable').click()
1354# step 5
1355 # searching won't change the sequentiality
1356 self.driver.find_element_by_id("search").clear()
1357 self.driver.find_element_by_id("search").send_keys("lib")
1358 self.driver.find_element_by_css_selector("button.btn").click()
1359 variable_list = self.get_table_column_text('class', 'variable_name')
1360 self.failUnless(is_list_sequenced(variable_list))
1361
1362
1363 ##############
1364 # CASE 923 #
1365 ##############
1366 def test_923(self):
1367 self.case_no = self.get_case_number()
1368 self.log.info(' CASE %s log: ' % str(self.case_no))
1369 self.driver.maximize_window()
1370 self.driver.get(self.base_url)
1371 # Step 2
1372 # default sequence in "Completed on" column is inverted
1373 c_list = self.get_table_column_text('class', 'completed_on')
1374 self.failUnless(is_list_inverted(c_list))
1375 # step 3
1376 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1377 self.driver.find_element_by_id("started_on").click()
1378 self.driver.find_element_by_id("log").click()
1379 self.driver.find_element_by_id("time").click()
1380 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1381 head_list = self.get_table_head_text('otable')
1382 for item in ['Outcome', 'Target', 'Machine', 'Started on', 'Completed on', 'Failed tasks', 'Errors', 'Warnings', 'Warnings', 'Time']:
1383 self.failUnless(item in head_list)
1384
1385
1386 ##############
1387 # CASE 924 #
1388 ##############
1389 def test_924(self):
1390 self.case_no = self.get_case_number()
1391 self.log.info(' CASE %s log: ' % str(self.case_no))
1392 self.driver.maximize_window()
1393 self.driver.get(self.base_url)
1394 # Please refer to case 924 requirement
1395 # default sequence in "Completed on" column is inverted
1396 c_list = self.get_table_column_text('class', 'completed_on')
1397 self.failUnless(is_list_inverted(c_list))
1398 # Step 4
1399 # click Errors , order in "Completed on" should be disturbed. Then hide
1400 # error column to check if order in "Completed on" can be restored
1401 self.find_element_by_link_text_in_table('otable', 'Errors').click()
1402 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1403 self.driver.find_element_by_id("errors_no").click()
1404 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1405 # Note: without time.sleep here, there'll be unpredictable error..TBD
1406 time.sleep(1)
1407 c_list = self.get_table_column_text('class', 'completed_on')
1408 self.failUnless(is_list_inverted(c_list))
1409
1410
1411 ##############
1412 # CASE 940 #
1413 ##############
1414 def test_940(self):
1415 self.case_no = self.get_case_number()
1416 self.log.info(' CASE %s log: ' % str(self.case_no))
1417 self.driver.maximize_window()
1418 self.driver.get(self.base_url)
1419 self.driver.find_element_by_link_text("core-image-minimal").click()
1420# Step 2-3
1421 self.find_element_by_link_text_in_table('nav', 'Packages').click()
1422 check_head_list = ['Package', 'Package version', 'Size', 'Recipe']
1423 head_list = self.get_table_head_text('otable')
1424 self.failUnless(head_list == check_head_list)
1425# Step 4
1426 # pulldown menu
1427 option_ids = ['recipe__layer_version__layer__name', 'recipe__layer_version__branch', \
1428 'recipe__layer_version__layer__commit', 'recipe__layer_version__layer__local_path', \
1429 'license', 'recipe__version']
1430 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1431 for item in option_ids:
1432 if not self.driver.find_element_by_id(item).is_selected():
1433 self.driver.find_element_by_id(item).click()
1434 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1435 # save screen here to observe that 'Package' and 'Package version' is
1436 # not selectable
1437 self.browser_delay()
1438 self.save_screenshot(screenshot_type='selenium', append_name='step4')
1439
1440
1441 ##############
1442 # CASE 941 #
1443 ##############
1444 def test_941(self):
1445 self.case_no = self.get_case_number()
1446 self.log.info(' CASE %s log: ' % str(self.case_no))
1447 self.driver.maximize_window()
1448 self.driver.get(self.base_url)
1449 self.driver.find_element_by_link_text("core-image-minimal").click()
1450 # Step 2-3
1451 self.find_element_by_link_text_in_table('nav', 'Packages').click()
1452 # column -- Package
1453 column_list = self.get_table_column_text_by_column_number('otable', 1)
1454 self.failUnless(is_list_sequenced(column_list))
1455 self.find_element_by_link_text_in_table('otable', 'Size').click()
1456
1457
1458 ##############
1459 # CASE 944 #
1460 ##############
1461 def test_944(self):
1462 self.case_no = self.get_case_number()
1463 self.log.info(' CASE %s log: ' % str(self.case_no))
1464 self.driver.maximize_window()
1465 self.driver.get(self.base_url)
1466 self.driver.find_element_by_link_text("core-image-minimal").click()
1467 # step 1: test Recipes page stuff
1468 self.driver.find_element_by_link_text("Recipes").click()
1469 # for these 3 items, default status is not-checked
1470 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1471 self.driver.find_element_by_id("layer_version__branch").click()
1472 self.driver.find_element_by_id("layer_version__layer__commit").click()
1473 self.driver.find_element_by_id("layer_version__layer__local_path").click()
1474 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1475 # otable is the recipes table here
1476 otable_head_text = self.get_table_head_text('otable')
1477 for item in ["Layer", "Layer branch", "Layer commit", "Layer directory"]:
1478 self.failIf(item not in otable_head_text)
1479 # click the fist recipe, whatever it is
1480 self.get_table_element("otable", 1, 1).click()
1481 self.failUnless(self.is_text_present(["Layer", "Layer branch", "Layer commit", "Layer directory", "Recipe file"]))
1482
1483 # step 2: test Packages page stuff. almost same as above
1484 self.driver.back()
1485 self.browser_delay()
1486 self.driver.find_element_by_link_text("Packages").click()
1487 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1488 self.driver.find_element_by_id("recipe__layer_version__layer__name").click()
1489 self.driver.find_element_by_id("recipe__layer_version__branch").click()
1490 self.driver.find_element_by_id("recipe__layer_version__layer__commit").click()
1491 self.driver.find_element_by_id("recipe__layer_version__layer__local_path").click()
1492 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1493 otable_head_text = self.get_table_head_text("otable")
1494 for item in ["Layer", "Layer branch", "Layer commit", "Layer directory"]:
1495 self.failIf(item not in otable_head_text)
1496 # click the fist recipe, whatever it is
1497 self.get_table_element("otable", 1, 1).click()
1498 self.failUnless(self.is_text_present(["Layer", "Layer branch", "Layer commit", "Layer directory"]))
1499
1500 # step 3: test Packages core-image-minimal(images) stuff. almost same as above. Note when future element-id changes...
1501 self.driver.back()
1502 self.driver.find_element_by_link_text("core-image-minimal").click()
1503 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1504 self.driver.find_element_by_id("layer_name").click()
1505 self.driver.find_element_by_id("layer_branch").click()
1506 self.driver.find_element_by_id("layer_commit").click()
1507 self.driver.find_element_by_id("layer_directory").click()
1508 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1509 otable_head_text = self.get_table_head_text("otable")
1510 for item in ["Layer", "Layer branch", "Layer commit", "Layer directory"]:
1511 self.failIf(item not in otable_head_text)
1512 # click the fist recipe, whatever it is
1513 self.get_table_element("otable", 1, 1).click()
1514 self.failUnless(self.is_text_present(["Layer", "Layer branch", "Layer commit", "Layer directory"]))
1515
1516 # step 4: check Configuration page
1517 self.driver.back()
1518 self.driver.find_element_by_link_text("Configuration").click()
1519 otable_head_text = self.get_table_head_text()
1520 for item in ["Layer", "Layer branch", "Layer commit", "Layer directory"]:
1521 self.failIf(item not in otable_head_text)
1522
1523
1524 ##############
1525 # CASE 945 #
1526 ##############
1527 def test_945(self):
1528 self.case_no = self.get_case_number()
1529 self.log.info(' CASE %s log: ' % str(self.case_no))
1530 self.driver.maximize_window()
1531 for items in ["Packages", "Recipes", "Tasks"]:
1532 self.driver.get(self.base_url)
1533 self.driver.find_element_by_link_text("core-image-minimal").click()
1534 self.driver.find_element_by_link_text(items).click()
1535
1536 # this may be page specific. If future page content changes, try to replace it with new xpath
1537 xpath_showrows = "/html/body/div[2]/div/div[2]/div[2]/div[2]/div/div/div[2]/select"
1538 xpath_table = "/html/body/div[2]/div/div[2]/div[2]/table/tbody"
1539 self.driver.find_element_by_xpath(xpath_showrows).click()
1540 rows_displayed = int(self.driver.find_element_by_xpath(xpath_showrows + "/option[2]").text)
1541
1542 # 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
1543 # Sure we can use driver.get(url) to refresh page, but since page will vary, we use click link text here
1544 self.driver.find_element_by_link_text(items).click()
1545 Select(self.driver.find_element_by_css_selector("select.pagesize")).select_by_visible_text(str(rows_displayed))
1546 self.failUnless(self.is_element_present(By.XPATH, xpath_table + "/tr[" + str(rows_displayed) +"]"))
1547 self.failIf(self.is_element_present(By.XPATH, xpath_table + "/tr[" + str(rows_displayed+1) +"]"))
1548
1549 # click 1st package, then go back to check if it's still those rows shown.
1550 self.driver.find_element_by_xpath(xpath_table + "/tr[1]/td[1]").click()
1551 self.driver.find_element_by_link_text(items).click()
1552 self.failUnless(self.is_element_present(By.XPATH, xpath_table + "/tr[" + str(rows_displayed) +"]"))
1553 self.failIf(self.is_element_present(By.XPATH, xpath_table + "/tr[" + str(rows_displayed+1) +"]"))
1554
1555
1556 ##############
1557 # CASE 946 #
1558 ##############
1559 def test_946(self):
1560 self.case_no = self.get_case_number()
1561 self.log.info(' CASE %s log: ' % str(self.case_no))
1562 self.driver.maximize_window()
1563 self.driver.get(self.base_url)
1564 self.driver.find_element_by_link_text("core-image-minimal").click()
1565 self.driver.find_element_by_link_text("Configuration").click()
1566 # step 3-4
1567 check_list = ["Summary", "BitBake variables"]
1568 for item in check_list:
1569 if not self.is_element_present(how=By.LINK_TEXT, what=item):
1570 self.log.error("%s not found" %item)
1571 if not self.is_text_present(['Layers', 'Layer', 'Layer branch', 'Layer commit', 'Layer directory']):
1572 self.log.error("text not found")
1573 # step 5
1574 self.driver.find_element_by_link_text("BitBake variables").click()
1575 if not self.is_text_present(['Variable', 'Value', 'Set in file', 'Description']):
1576 self.log.error("text not found")
1577 # This may be unstable because it's page-specific
1578 # step 6: this is how we find filter beside "Set in file"
1579 temp_element = self.find_element_by_text_in_table('otable', "Set in file")
1580 temp_element.find_element_by_xpath("..//*/a/i[@class='icon-filter filtered']").click()
1581 self.browser_delay()
1582 self.driver.find_element_by_xpath("(//input[@name='filter'])[2]").click()
1583 self.driver.find_element_by_css_selector("button.btn.btn-primary").click()
1584 # save screen here
1585 self.browser_delay()
1586 self.save_screenshot(screenshot_type='selenium', append_name='step6')
1587 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1588 # save screen here
1589 # step 7
1590 # we should manually check the step 6-8 result using screenshot
1591 self.browser_delay()
1592 self.save_screenshot(screenshot_type='selenium', append_name='step7')
1593 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1594 # step 9
1595 # click the 1st item, no matter what it is
1596 self.driver.find_element_by_xpath("//*[@id='otable']/tbody/tr[1]/td[1]/a").click()
1597 # give it 1 sec so the pop-up becomes the "active_element"
1598 time.sleep(1)
1599 element = self.driver.switch_to.active_element
1600 check_list = ['Order', 'Configuration file', 'Operation', 'Line number']
1601 for item in check_list:
1602 if item not in element.text:
1603 self.log.error("%s not found" %item)
1604 # any better way to close this pop-up? ... TBD
1605 element.find_element_by_xpath(".//*[@class='close']").click()
1606 # step 10 : need to manually check "Yocto Manual" in saved screen
1607 self.driver.find_element_by_css_selector("i.icon-share.get-info").click()
1608 # save screen here
1609 time.sleep(5)
1610 self.save_screenshot(screenshot_type='native', append_name='step10')
1611
1612
1613 ##############
1614 # CASE 947 #
1615 ##############
1616 def test_947(self):
1617 self.case_no = self.get_case_number()
1618 self.log.info(' CASE %s log: ' % str(self.case_no))
1619 self.driver.maximize_window()
1620 self.driver.get(self.base_url)
1621 self.driver.find_element_by_link_text("core-image-minimal").click()
1622 self.find_element_by_link_text_in_table('nav', 'Configuration').click()
1623 # step 2
1624 self.driver.find_element_by_link_text("BitBake variables").click()
1625 # step 3
1626 def xpath_option(column_name):
1627 # return xpath of options under "Edit columns" button
1628 return self.shortest_xpath('id', 'navTab') + self.shortest_xpath('id', 'editcol') \
1629 + self.shortest_xpath('id', column_name)
1630 self.find_element_by_xpath_in_table('navTab', self.shortest_xpath('class', 'btn dropdown-toggle')).click()
1631 # by default, option "Description" and "Set in file" were checked
1632 self.driver.find_element_by_xpath(xpath_option('description')).click()
1633 self.driver.find_element_by_xpath(xpath_option('file')).click()
1634 self.find_element_by_xpath_in_table('navTab', self.shortest_xpath('class', 'btn dropdown-toggle')).click()
1635 check_list = ['Description', 'Set in file']
1636 head_list = self.get_table_head_text('otable')
1637 for item in check_list:
1638 self.failIf(item in head_list)
1639 # check these 2 options and verify again
1640 self.find_element_by_xpath_in_table('navTab', self.shortest_xpath('class', 'btn dropdown-toggle')).click()
1641 self.driver.find_element_by_xpath(xpath_option('description')).click()
1642 self.driver.find_element_by_xpath(xpath_option('file')).click()
1643 self.find_element_by_xpath_in_table('navTab', self.shortest_xpath('class', 'btn dropdown-toggle')).click()
1644 head_list = self.get_table_head_text('otable')
1645 for item in check_list:
1646 self.failUnless(item in head_list)
1647
1648
1649 ##############
1650 # CASE 948 #
1651 ##############
1652 def test_948(self):
1653 self.case_no = self.get_case_number()
1654 self.log.info(' CASE %s log: ' % str(self.case_no))
1655 self.driver.maximize_window()
1656 self.driver.get(self.base_url)
1657 self.driver.find_element_by_link_text("core-image-minimal").click()
1658 self.find_element_by_link_text_in_table('nav', 'Configuration').click()
1659 self.driver.find_element_by_link_text("BitBake variables").click()
1660 number_before_search = list()
1661 number_after_search = list()
1662 # step 3
1663 # temp_dict -- filter string : filter name in firepath
1664 temp_dict = {'Set in file':'filter_vhistory__file_name', 'Description':'filter_description'}
1665 count = 0
1666 for key in temp_dict:
1667 try:
1668 temp_element = self.find_element_by_text_in_table('otable', key)
1669 temp_element.find_element_by_xpath("..//*[@class='icon-filter filtered']").click()
1670 # delay here. otherwise it won't get correct "text" we need.
1671 # TBD
1672 time.sleep(1)
1673 # step 4-5, we need to make sure that "search" manipulation
1674 # does reduce the number in the filter.
1675 # in this case, text returned will be "All variables (xxx)"
1676 temp_text = self.driver.find_element_by_xpath("//*[@id='" + temp_dict[key] + "']//*[@class='radio']").text
1677 number_list = extract_number_from_string(temp_text)
1678 print number_list
1679 # probably we only need the first number. in this case.
1680 number_before_search.append(eval(number_list[0]))
1681 count +=1
1682 # how we locate the close button
1683 self.driver.find_element_by_xpath("//*[@id='" + temp_dict[key] + "']//*[@class='close']").click()
1684 self.browser_delay()
1685 except Exception,e:
1686 self.log.error(e)
1687 raise
1688 # search for a while...
1689 self.driver.find_element_by_id("search").clear()
1690 self.driver.find_element_by_id("search").send_keys("BB")
1691 self.driver.find_element_by_css_selector("button.btn").click()
1692 # same operation as above, only to get the new numbers in the filter
1693 count = 0
1694 for key in temp_dict:
1695 try:
1696 temp_element = self.find_element_by_text_in_table('otable', key)
1697 temp_element.find_element_by_xpath("..//*[@class='icon-filter filtered']").click()
1698 time.sleep(1)
1699 # in this case, text returned will be "All variables (xxx)"
1700 temp_text = self.driver.find_element_by_xpath("//*[@id='" + temp_dict[key] + "']//*[@class='radio']").text
1701 number_list = extract_number_from_string(temp_text)
1702 # probably we only need the first number. in this case.
1703 number_after_search.append(eval(number_list[0]))
1704 count += 1
1705 # how we locate the close button
1706 self.driver.find_element_by_xpath("//*[@id='" + temp_dict[key] + "']//*[@class='close']").click()
1707 self.browser_delay()
1708 except Exception,e:
1709 self.log.error(e)
1710 raise
1711 for i in range(0, count):
1712 print i
1713 print number_after_search[i]
1714 print number_before_search[i]
1715 if number_after_search[i] < number_before_search[i]:
1716 self.log.info("After search, filter number reduces")
1717 else:
1718 self.log.error("Error: After search, filter number doesn't reduce")
1719 self.failIf(True)
1720
1721
1722 ##############
1723 # CASE 949 #
1724 ##############
1725 def test_949(self):
1726 self.case_no = self.get_case_number()
1727 self.log.info(' CASE %s log: ' % str(self.case_no))
1728 self.driver.maximize_window()
1729 self.driver.get(self.base_url)
1730 self.driver.find_element_by_link_text("core-image-minimal").click()
1731 self.find_element_by_link_text_in_table('nav', 'core-image-minimal').click()
1732 # step 3
1733 try:
1734 self.driver.find_element_by_partial_link_text("Packages included")
1735 self.driver.find_element_by_partial_link_text("Directory structure")
1736 except Exception,e:
1737 self.log.error(e)
1738 self.failIf(True)
1739 # step 4
1740 head_list = self.get_table_head_text('otable')
1741 for item in ['Package', 'Package version', 'Size', 'Dependencies', 'Reverse dependencies', 'Recipe']:
1742 self.failUnless(item in head_list)
1743 # step 5-6
1744 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1745 selectable_class = 'checkbox'
1746 # minimum-table : means unselectable items
1747 unselectable_class = 'checkbox muted'
1748 selectable_check_list = ['Dependencies', 'Layer', 'Layer branch', 'Layer commit', 'Layer directory', \
1749 'License', 'Recipe', 'Recipe version', 'Reverse dependencies', \
1750 'Size', 'Size over total (%)']
1751 unselectable_check_list = ['Package', 'Package version']
1752 selectable_list = list()
1753 unselectable_list = list()
1754 selectable_elements = self.driver.find_elements_by_xpath("//*[@id='editcol']//*[@class='" + selectable_class + "']")
1755 unselectable_elements = self.driver.find_elements_by_xpath("//*[@id='editcol']//*[@class='" + unselectable_class + "']")
1756 for element in selectable_elements:
1757 selectable_list.append(element.text)
1758 for element in unselectable_elements:
1759 unselectable_list.append(element.text)
1760 # check them
1761 for item in selectable_check_list:
1762 if item not in selectable_list:
1763 self.log.error(" %s not found in dropdown menu \n" % item)
1764 self.failIf(True)
1765 for item in unselectable_check_list:
1766 if item not in unselectable_list:
1767 self.log.error(" %s not found in dropdown menu \n" % item)
1768 self.failIf(True)
1769 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1770 # step 7
1771 self.driver.find_element_by_partial_link_text("Directory structure").click()
1772 head_list = self.get_table_head_text('dirtable')
1773 for item in ['Directory / File', 'Symbolic link to', 'Source package', 'Size', 'Permissions', 'Owner', 'Group']:
1774 if item not in head_list:
1775 self.log.error(" %s not found in Directory structure table head \n" % item)
1776 self.failIf(True)
1777
1778
1779 ##############
1780 # CASE 950 #
1781 ##############
1782 def test_950(self):
1783 self.case_no = self.get_case_number()
1784 self.log.info(' CASE %s log: ' % str(self.case_no))
1785 self.driver.maximize_window()
1786 self.driver.get(self.base_url)
1787 # step3&4: so far we're not sure if there's "successful build" or "failed
1788 # build".If either of them doesn't exist, we can still go on other steps
1789 check_list = ['Configuration', 'Tasks', 'Recipes', 'Packages', 'Time', 'CPU usage', 'Disk I/O']
1790 has_successful_build = 1
1791 has_failed_build = 1
1792 try:
1793 pass_icon = self.driver.find_element_by_xpath("//*[@class='icon-ok-sign success']")
1794 except Exception:
1795 self.log.info("no successful build exists")
1796 has_successful_build = 0
1797 pass
1798 if has_successful_build:
1799 pass_icon.click()
1800 # save screen here to check if it matches requirement.
1801 self.browser_delay()
1802 self.save_screenshot(screenshot_type='selenium', append_name='step3_1')
1803 for item in check_list:
1804 try:
1805 self.find_element_by_link_text_in_table('nav', item)
1806 except Exception:
1807 self.log.error("link %s cannot be found in the page" % item)
1808 self.failIf(True)
1809 # step 6
1810 check_list_2 = ['Packages included', 'Total package size', \
1811 'License manifest', 'Image files']
1812 self.failUnless(self.is_text_present(check_list_2))
1813 self.driver.back()
1814 try:
1815 fail_icon = self.driver.find_element_by_xpath("//*[@class='icon-minus-sign error']")
1816 except Exception:
1817 has_failed_build = 0
1818 self.log.info("no failed build exists")
1819 pass
1820 if has_failed_build:
1821 fail_icon.click()
1822 # save screen here to check if it matches requirement.
1823 self.browser_delay()
1824 self.save_screenshot(screenshot_type='selenium', append_name='step3_2')
1825 for item in check_list:
1826 try:
1827 self.find_element_by_link_text_in_table('nav', item)
1828 except Exception:
1829 self.log.error("link %s cannot be found in the page" % item)
1830 self.failIf(True)
1831 # step 7 involved
1832 check_list_3 = ['Machine', 'Distro', 'Layers', 'Total number of tasks', 'Tasks executed', \
1833 'Tasks not executed', 'Reuse', 'Recipes built', 'Packages built']
1834 self.failUnless(self.is_text_present(check_list_3))
1835 self.driver.back()
1836
1837
1838 ##############
1839 # CASE 951 #
1840 ##############
1841 def test_951(self):
1842 self.case_no = self.get_case_number()
1843 self.log.info(' CASE %s log: ' % str(self.case_no))
1844 self.driver.maximize_window()
1845 self.driver.get(self.base_url)
1846 # currently test case itself isn't responsible for creating "1 successful and
1847 # 1 failed build"
1848 has_successful_build = 1
1849 has_failed_build = 1
1850 try:
1851 fail_icon = self.driver.find_element_by_xpath("//*[@class='icon-minus-sign error']")
1852 except Exception:
1853 has_failed_build = 0
1854 self.log.info("no failed build exists")
1855 pass
1856 # if there's failed build, we can proceed
1857 if has_failed_build:
1858 self.driver.find_element_by_partial_link_text("error").click()
1859 self.driver.back()
1860 # not sure if there "must be" some warnings, so here save a screen
1861 self.browser_delay()
1862 self.save_screenshot(screenshot_type='selenium', append_name='step4')
1863
1864
1865 ##############
1866 # CASE 955 #
1867 ##############
1868 def test_955(self):
1869 self.case_no = self.get_case_number()
1870 self.log.info(' CASE %s log: ' % str(self.case_no))
1871 self.driver.maximize_window()
1872 self.driver.get(self.base_url)
1873 self.log.info(" You should manually create all images before test starts!")
1874 # So far the case itself is not responsable for creating all sorts of images.
1875 # So assuming they are already there
1876 # step 2
1877 self.driver.find_element_by_link_text("core-image-minimal").click()
1878 # save screen here to see the page component
1879
1880
1881 ##############
1882 # CASE 956 #
1883 ##############
1884 def test_956(self):
1885 self.case_no = self.get_case_number()
1886 self.log.info(' CASE %s log: ' % str(self.case_no))
1887 self.driver.maximize_window()
1888 self.driver.get(self.base_url)
1889 # step 2-3 need to run manually
1890 self.log.info("step 2-3: checking the help message when you hover on help icon of target,\
1891 tasks, recipes, packages need to run manually")
1892 self.driver.find_element_by_partial_link_text("Toaster manual").click()
1893 if not self.is_text_present("Toaster Manual"):
1894 self.log.error("please check [Toaster manual] link on page")
1895 self.failIf(True)
1896
1897
1898 ##############
1899 # CASE 959 #
1900 ##############
1901 def test_959(self):
1902 self.case_no = self.get_case_number()
1903 self.log.info(' CASE %s log: ' % str(self.case_no))
1904 self.driver.maximize_window()
1905 self.driver.get(self.base_url)
1906 self.driver.find_element_by_link_text("core-image-minimal").click()
1907 # step 2-3
1908 self.find_element_by_link_text_in_table('nav', 'Tasks').click()
1909 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1910 self.driver.find_element_by_id("task_log").click()
1911 self.driver.find_element_by_css_selector("button.btn.dropdown-toggle").click()
1912 # step 4: "Not Executed" tasks have no log. So click "Log"...
1913 self.find_element_by_link_text_in_table('otable', 'Log').click()
1914 # save screen to see if there's "absolute path" of logs
1915 self.browser_delay()
1916 self.save_screenshot(screenshot_type='selenium', append_name='step4_1')
1917 self.find_element_by_link_text_in_table('otable', 'Log').click()
1918 # save screen to see if there's "absolute path" of logs
1919 self.browser_delay()
1920 self.save_screenshot(screenshot_type='selenium', append_name='step4_2')
1921
1922
1923
1924
diff --git a/bitbake/lib/toaster/contrib/tts/toasteruitest/toaster_test.cfg b/bitbake/lib/toaster/contrib/tts/toasteruitest/toaster_test.cfg
new file mode 100644
index 0000000000..6405f9a8ef
--- /dev/null
+++ b/bitbake/lib/toaster/contrib/tts/toasteruitest/toaster_test.cfg
@@ -0,0 +1,21 @@
1# Configuration file for toaster_test
2# Sorted by different host type
3
4# test browser could be: firefox; chrome; ie(still under development)
5# logging_level could be: CRITICAL; ERROR; WARNING; INFO; DEBUG; NOTSET
6
7
8[toaster_test_linux]
9toaster_url = 'http://127.0.0.1:8000'
10test_browser = 'firefox'
11test_cases = [946]
12logging_level = 'INFO'
13
14
15[toaster_test_windows]
16toaster_url = 'http://127.0.0.1:8000'
17test_browser = ['ie', 'firefox', 'chrome']
18test_cases = [901, 902, 903]
19logging_level = 'DEBUG'
20
21
diff --git a/bitbake/lib/toaster/contrib/tts/urlcheck.py b/bitbake/lib/toaster/contrib/tts/urlcheck.py
new file mode 100644
index 0000000000..a94af5000b
--- /dev/null
+++ b/bitbake/lib/toaster/contrib/tts/urlcheck.py
@@ -0,0 +1,44 @@
1from __future__ import print_function
2import sys
3
4import httplib2
5import time
6
7import config
8import urllist
9
10# TODO: spawn server here
11BASEURL="http://localhost:8000/"
12
13#def print_browserlog(url):
14# driver = webdriver.Firefox()
15# driver.get(url)
16# body = driver.find_element_by_tag_name("body")
17# body.send_keys(Keys.CONTROL + 't')
18# for i in driver.get_log('browser'):
19# print(i)
20# driver.close()
21
22
23# TODO: turn to a test
24def validate_html(url):
25 h = httplib2.Http(".cache")
26 # TODO: the w3c-validator must be a configurable setting
27 urlrequest = "http://icarus.local/w3c-validator/check?doctype=HTML5&uri="+url
28 try:
29 resp, content = h.request(urlrequest, "HEAD")
30 if resp['x-w3c-validator-status'] == "Abort":
31 config.logger.error("FAILed call %s" % url)
32 else:
33 config.logger.error("url %s is %s\terrors %s warnings %s (check at %s)" % (url, resp['x-w3c-validator-status'], resp['x-w3c-validator-errors'], resp['x-w3c-validator-warnings'], urlrequest))
34 except Exception as e:
35 config.logger.warn("Failed validation call: %s" % e.__str__())
36
37 print("done %s" % url)
38
39if __name__ == "__main__":
40 if len(sys.argv) > 1:
41 validate_html(sys.argv[1])
42 else:
43 for url in urllist.URLS:
44 validate_html(BASEURL+url)
diff --git a/bitbake/lib/toaster/contrib/tts/urllist.py b/bitbake/lib/toaster/contrib/tts/urllist.py
new file mode 100644
index 0000000000..a7d6d6ec4e
--- /dev/null
+++ b/bitbake/lib/toaster/contrib/tts/urllist.py
@@ -0,0 +1,53 @@
1import config
2
3URLS = [
4'toastergui/landing/',
5'toastergui/builds/',
6'toastergui/build/1',
7'toastergui/build/1/tasks/',
8'toastergui/build/1/tasks/1/',
9'toastergui/build/1/task/1',
10'toastergui/build/1/recipes/',
11'toastergui/build/1/recipe/1/active_tab/1',
12'toastergui/build/1/recipe/1',
13'toastergui/build/1/recipe_packages/1',
14'toastergui/build/1/packages/',
15'toastergui/build/1/package/1',
16'toastergui/build/1/package_built_dependencies/1',
17'toastergui/build/1/package_included_detail/1/1',
18'toastergui/build/1/package_included_dependencies/1/1',
19'toastergui/build/1/package_included_reverse_dependencies/1/1',
20'toastergui/build/1/target/1',
21'toastergui/build/1/target/1/targetpkg',
22'toastergui/dentries/build/1/target/1',
23'toastergui/build/1/target/1/dirinfo',
24'toastergui/build/1/target/1/dirinfo_filepath/_/bin/bash',
25'toastergui/build/1/configuration',
26'toastergui/build/1/configvars',
27'toastergui/build/1/buildtime',
28'toastergui/build/1/cpuusage',
29'toastergui/build/1/diskio',
30'toastergui/build/1/target/1/packagefile/1',
31'toastergui/newproject/',
32'toastergui/projects/',
33'toastergui/project/',
34'toastergui/project/1',
35'toastergui/project/1/configuration',
36'toastergui/project/1/builds/',
37'toastergui/project/1/layers/',
38'toastergui/project/1/layer/1',
39'toastergui/project/1/layer/',
40'toastergui/project/1/importlayer',
41'toastergui/project/1/targets/',
42'toastergui/project/1/machines/',
43'toastergui/xhr_build/',
44'toastergui/xhr_projectbuild/1/',
45'toastergui/xhr_projectinfo/',
46'toastergui/xhr_projectedit/1',
47'toastergui/xhr_configvaredit/1',
48'toastergui/xhr_datatypeahead/1',
49'toastergui/xhr_importlayer/',
50'toastergui/xhr_updatelayer/',
51'toastergui/project/1/buildrequest/1',
52'toastergui/',
53]