summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/toaster/tests/functional/functional_helpers.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/toaster/tests/functional/functional_helpers.py')
-rw-r--r--bitbake/lib/toaster/tests/functional/functional_helpers.py168
1 files changed, 141 insertions, 27 deletions
diff --git a/bitbake/lib/toaster/tests/functional/functional_helpers.py b/bitbake/lib/toaster/tests/functional/functional_helpers.py
index 5c4ea71794..e28f2024f5 100644
--- a/bitbake/lib/toaster/tests/functional/functional_helpers.py
+++ b/bitbake/lib/toaster/tests/functional/functional_helpers.py
@@ -11,35 +11,58 @@ import os
11import logging 11import logging
12import subprocess 12import subprocess
13import signal 13import signal
14import time
15import re 14import re
15import requests
16 16
17from django.urls import reverse
17from tests.browser.selenium_helpers_base import SeleniumTestCaseBase 18from tests.browser.selenium_helpers_base import SeleniumTestCaseBase
18from tests.builds.buildtest import load_build_environment 19from selenium.webdriver.common.by import By
20from selenium.webdriver.support.select import Select
21from selenium.common.exceptions import NoSuchElementException
19 22
20logger = logging.getLogger("toaster") 23logger = logging.getLogger("toaster")
24toaster_processes = []
21 25
22class SeleniumFunctionalTestCase(SeleniumTestCaseBase): 26class SeleniumFunctionalTestCase(SeleniumTestCaseBase):
23 wait_toaster_time = 5 27 wait_toaster_time = 10
24 28
25 @classmethod 29 @classmethod
26 def setUpClass(cls): 30 def setUpClass(cls):
27 # So that the buildinfo helper uses the test database' 31 # So that the buildinfo helper uses the test database'
28 if os.environ.get('DJANGO_SETTINGS_MODULE', '') != \ 32 if os.environ.get('DJANGO_SETTINGS_MODULE', '') != \
29 'toastermain.settings_test': 33 'toastermain.settings_test':
30 raise RuntimeError("Please initialise django with the tests settings: " \ 34 raise RuntimeError("Please initialise django with the tests settings: "
31 "DJANGO_SETTINGS_MODULE='toastermain.settings_test'") 35 "DJANGO_SETTINGS_MODULE='toastermain.settings_test'")
32 36
33 load_build_environment() 37 # Wait for any known toaster processes to exit
38 global toaster_processes
39 for toaster_process in toaster_processes:
40 try:
41 os.waitpid(toaster_process, os.WNOHANG)
42 except ChildProcessError:
43 pass
34 44
35 # start toaster 45 # start toaster
36 cmd = "bash -c 'source toaster start'" 46 cmd = "bash -c 'source toaster start'"
37 p = subprocess.Popen( 47 start_process = subprocess.Popen(
38 cmd, 48 cmd,
39 cwd=os.environ.get("BUILDDIR"), 49 cwd=os.environ.get("BUILDDIR"),
40 shell=True) 50 shell=True)
41 if p.wait() != 0: 51 toaster_processes = [start_process.pid]
42 raise RuntimeError("Can't initialize toaster") 52 if start_process.wait() != 0:
53 port_use = os.popen("lsof -i -P -n | grep '8000 (LISTEN)'").read().strip()
54 message = ''
55 if port_use:
56 process_id = port_use.split()[1]
57 process = os.popen(f"ps -o cmd= -p {process_id}").read().strip()
58 message = f"Port 8000 occupied by {process}"
59 raise RuntimeError(f"Can't initialize toaster. {message}")
60
61 builddir = os.environ.get("BUILDDIR")
62 with open(os.path.join(builddir, '.toastermain.pid'), 'r') as f:
63 toaster_processes.append(int(f.read()))
64 with open(os.path.join(builddir, '.runbuilds.pid'), 'r') as f:
65 toaster_processes.append(int(f.read()))
43 66
44 super(SeleniumFunctionalTestCase, cls).setUpClass() 67 super(SeleniumFunctionalTestCase, cls).setUpClass()
45 cls.live_server_url = 'http://localhost:8000/' 68 cls.live_server_url = 'http://localhost:8000/'
@@ -48,22 +71,30 @@ class SeleniumFunctionalTestCase(SeleniumTestCaseBase):
48 def tearDownClass(cls): 71 def tearDownClass(cls):
49 super(SeleniumFunctionalTestCase, cls).tearDownClass() 72 super(SeleniumFunctionalTestCase, cls).tearDownClass()
50 73
51 # XXX: source toaster stop gets blocked, to review why? 74 global toaster_processes
52 # from now send SIGTERM by hand
53 time.sleep(cls.wait_toaster_time)
54 builddir = os.environ.get("BUILDDIR")
55 75
56 with open(os.path.join(builddir, '.toastermain.pid'), 'r') as f: 76 cmd = "bash -c 'source toaster stop'"
57 toastermain_pid = int(f.read()) 77 stop_process = subprocess.Popen(
58 os.kill(toastermain_pid, signal.SIGTERM) 78 cmd,
59 with open(os.path.join(builddir, '.runbuilds.pid'), 'r') as f: 79 cwd=os.environ.get("BUILDDIR"),
60 runbuilds_pid = int(f.read()) 80 shell=True)
61 os.kill(runbuilds_pid, signal.SIGTERM) 81 # Toaster stop has been known to hang in these tests so force kill if it stalls
82 try:
83 if stop_process.wait(cls.wait_toaster_time) != 0:
84 raise Exception('Toaster stop process failed')
85 except Exception as e:
86 if e is subprocess.TimeoutExpired:
87 print('Toaster stop process took too long. Force killing toaster...')
88 else:
89 print('Toaster stop process failed. Force killing toaster...')
90 stop_process.kill()
91 for toaster_process in toaster_processes:
92 os.kill(toaster_process, signal.SIGTERM)
62 93
63 94
64 def get_URL(self): 95 def get_URL(self):
65 rc=self.get_page_source() 96 rc=self.get_page_source()
66 project_url=re.search("(projectPageUrl\s:\s\")(.*)(\",)",rc) 97 project_url=re.search(r"(projectPageUrl\s:\s\")(.*)(\",)",rc)
67 return project_url.group(2) 98 return project_url.group(2)
68 99
69 100
@@ -74,8 +105,8 @@ class SeleniumFunctionalTestCase(SeleniumTestCaseBase):
74 """ 105 """
75 try: 106 try:
76 table_element = self.get_table_element(table_id) 107 table_element = self.get_table_element(table_id)
77 element = table_element.find_element_by_link_text(link_text) 108 element = table_element.find_element(By.LINK_TEXT, link_text)
78 except self.NoSuchElementException: 109 except NoSuchElementException:
79 print('no element found') 110 print('no element found')
80 raise 111 raise
81 return element 112 return element
@@ -85,8 +116,8 @@ class SeleniumFunctionalTestCase(SeleniumTestCaseBase):
85#return whole-table element 116#return whole-table element
86 element_xpath = "//*[@id='" + table_id + "']" 117 element_xpath = "//*[@id='" + table_id + "']"
87 try: 118 try:
88 element = self.driver.find_element_by_xpath(element_xpath) 119 element = self.driver.find_element(By.XPATH, element_xpath)
89 except self.NoSuchElementException: 120 except NoSuchElementException:
90 raise 121 raise
91 return element 122 return element
92 row = coordinate[0] 123 row = coordinate[0]
@@ -95,8 +126,8 @@ class SeleniumFunctionalTestCase(SeleniumTestCaseBase):
95#return whole-row element 126#return whole-row element
96 element_xpath = "//*[@id='" + table_id + "']/tbody/tr[" + str(row) + "]" 127 element_xpath = "//*[@id='" + table_id + "']/tbody/tr[" + str(row) + "]"
97 try: 128 try:
98 element = self.driver.find_element_by_xpath(element_xpath) 129 element = self.driver.find_element(By.XPATH, element_xpath)
99 except self.NoSuchElementException: 130 except NoSuchElementException:
100 return False 131 return False
101 return element 132 return element
102#now we are looking for an element with specified X and Y 133#now we are looking for an element with specified X and Y
@@ -104,7 +135,90 @@ class SeleniumFunctionalTestCase(SeleniumTestCaseBase):
104 135
105 element_xpath = "//*[@id='" + table_id + "']/tbody/tr[" + str(row) + "]/td[" + str(column) + "]" 136 element_xpath = "//*[@id='" + table_id + "']/tbody/tr[" + str(row) + "]/td[" + str(column) + "]"
106 try: 137 try:
107 element = self.driver.find_element_by_xpath(element_xpath) 138 element = self.driver.find_element(By.XPATH, element_xpath)
108 except self.NoSuchElementException: 139 except NoSuchElementException:
109 return False 140 return False
110 return element 141 return element
142
143 def create_new_project(
144 self,
145 project_name,
146 release,
147 release_title,
148 merge_toaster_settings,
149 ):
150 """ Create/Test new project using:
151 - Project Name: Any string
152 - Release: Any string
153 - Merge Toaster settings: True or False
154 """
155
156 # Obtain a CSRF token from a suitable URL
157 projs = requests.get(self.live_server_url + reverse('newproject'))
158 csrftoken = projs.cookies.get('csrftoken')
159
160 # Use the projects typeahead to find out if the project already exists
161 req = requests.get(self.live_server_url + reverse('xhr_projectstypeahead'), {'search': project_name, 'format' : 'json'})
162 data = req.json()
163 # Delete any existing projects
164 for result in data['results']:
165 del_url = reverse('xhr_project', args=(result['id'],))
166 del_response = requests.delete(self.live_server_url + del_url, cookies={'csrftoken': csrftoken}, headers={'X-CSRFToken': csrftoken})
167 self.assertEqual(del_response.status_code, 200)
168
169 self.get(reverse('newproject'))
170 self.wait_until_visible('#new-project-name')
171 self.driver.find_element(By.ID,
172 "new-project-name").send_keys(project_name)
173
174 select = Select(self.find('#projectversion'))
175 select.select_by_value(release)
176
177 # check merge toaster settings
178 checkbox = self.find('.checkbox-mergeattr')
179 if merge_toaster_settings:
180 if not checkbox.is_selected():
181 checkbox.click()
182 else:
183 if checkbox.is_selected():
184 checkbox.click()
185
186 self.wait_until_clickable('#create-project-button')
187
188 self.driver.find_element(By.ID, "create-project-button").click()
189
190 element = self.wait_until_visible('#project-created-notification')
191 self.assertTrue(
192 self.element_exists('#project-created-notification'),
193 f"Project:{project_name} creation notification not shown"
194 )
195 self.assertTrue(
196 project_name in element.text,
197 f"New project name:{project_name} not in new project notification"
198 )
199
200 # Use the projects typeahead again to check the project now exists
201 req = requests.get(self.live_server_url + reverse('xhr_projectstypeahead'), {'search': project_name, 'format' : 'json'})
202 data = req.json()
203 self.assertGreater(len(data['results']), 0, f"New project:{project_name} not found in database")
204
205 project_id = data['results'][0]['id']
206
207 self.wait_until_visible('#project-release-title')
208
209 # check release
210 if release_title is not None:
211 self.assertTrue(re.search(
212 release_title,
213 self.driver.find_element(By.XPATH,
214 "//span[@id='project-release-title']"
215 ).text),
216 'The project release is not defined')
217
218 return project_id
219
220 def load_projects_page_helper(self):
221 self.wait_until_present('#projectstable')
222 # Need to wait for some data in the table too
223 self.wait_until_present('td[class="updated"]')
224