test.py 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034
  1. #!/usr/bin/env python
  2. #
  3. # Copyright 2018 Espressif Systems (Shanghai) PTE LTD
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. # Utility for testing the web server. Test cases:
  17. # Assume the device supports 'n' simultaneous open sockets
  18. #
  19. # HTTP Server Tests
  20. #
  21. # 0. Firmware Settings:
  22. # - Create a dormant thread whose sole job is to call httpd_stop() when instructed
  23. # - Measure the following before httpd_start() is called:
  24. # - current free memory
  25. # - current free sockets
  26. # - Measure the same whenever httpd_stop is called
  27. # - Register maximum possible URI handlers: should be successful
  28. # - Register one more URI handler: should fail
  29. # - Deregister on URI handler: should be successful
  30. # - Register on more URI handler: should succeed
  31. # - Register separate handlers for /hello, /hello/type_html. Also
  32. # ensure that /hello/type_html is registered BEFORE /hello. (tests
  33. # that largest matching URI is picked properly)
  34. # - Create URI handler /adder. Make sure it uses a custom free_ctx
  35. # structure to free it up
  36. # 1. Using Standard Python HTTP Client
  37. # - simple GET on /hello (returns Hello World. Ensures that basic
  38. # firmware tests are complete, or returns error)
  39. # - POST on /hello (should fail)
  40. # - PUT on /hello (should fail)
  41. # - simple POST on /echo (returns whatever the POST data)
  42. # - simple PUT on /echo (returns whatever the PUT data)
  43. # - GET on /echo (should fail)
  44. # - simple GET on /hello/type_html (returns Content type as text/html)
  45. # - simple GET on /hello/status_500 (returns HTTP status 500)
  46. # - simple GET on /false_uri (returns HTTP status 404)
  47. # - largest matching URI handler is picked is already verified because
  48. # of /hello and /hello/type_html tests
  49. #
  50. #
  51. # 2. Session Tests
  52. # - Sessions + Pipelining basics:
  53. # - Create max supported sessions
  54. # - On session i,
  55. # - send 3 back-to-back POST requests with data i on /adder
  56. # - read back 3 responses. They should be i, 2i and 3i
  57. # - Tests that
  58. # - pipelining works
  59. # - per-session context is maintained for all supported
  60. # sessions
  61. # - Close all sessions
  62. #
  63. # - Cleanup leftover data: Tests that the web server properly cleans
  64. # up leftover data
  65. # - Create a session
  66. # - POST on /leftover_data with 52 bytes of data (data includes
  67. # \r\n)(the handler only
  68. # reads first 10 bytes and returns them, leaving the rest of the
  69. # bytes unread)
  70. # - GET on /hello (should return 'Hello World')
  71. # - POST on /false_uri with 52 bytes of data (data includes \r\n)
  72. # (should return HTTP 404)
  73. # - GET on /hello (should return 'Hello World')
  74. #
  75. # - Test HTTPd Asynchronous response
  76. # - Create a session
  77. # - GET on /async_data
  78. # - returns 'Hello World!' as a response
  79. # - the handler schedules an async response, which generates a second
  80. # response 'Hello Double World!'
  81. #
  82. # - Spillover test
  83. # - Create max supported sessions with the web server
  84. # - GET /hello on all the sessions (should return Hello World)
  85. # - Create one more session, this should fail
  86. # - GET /hello on all the sessions (should return Hello World)
  87. #
  88. # - Timeout test
  89. # - Create a session and only Send 'GE' on the same (simulates a
  90. # client that left the network halfway through a request)
  91. # - Wait for recv-wait-timeout
  92. # - Server should automatically close the socket
  93. # ############ TODO TESTS #############
  94. # 3. Stress Tests
  95. #
  96. # - httperf
  97. # - Run the following httperf command:
  98. # httperf --server=10.31.130.126 --wsess=8,50,0.5 --rate 8 --burst-length 2
  99. #
  100. # - The above implies that the test suite will open
  101. # - 8 simultaneous connections with the server
  102. # - the rate of opening the sessions will be 8 per sec. So in our
  103. # case, a new connection will be opened every 0.2 seconds for 1 second
  104. # - The burst length 2 indicates that 2 requests will be sent
  105. # simultaneously on the same connection in a single go
  106. # - 0.5 seconds is the time between sending out 2 bursts
  107. # - 50 is the total number of requests that will be sent out
  108. #
  109. # - So in the above example, the test suite will open 8
  110. # connections, each separated by 0.2 seconds. On each connection
  111. # it will send 2 requests in a single burst. The bursts on a
  112. # single connection will be separated by 0.5 seconds. A total of
  113. # 25 bursts (25 x 2 = 50) will be sent out
  114. # 4. Leak Tests
  115. # - Simple Leak test
  116. # - Simple GET on /hello/restart (returns success, stop web server, measures leaks, restarts webserver)
  117. # - Simple GET on /hello/restart_results (returns the leak results)
  118. # - Leak test with open sockets
  119. # - Open 8 sessions
  120. # - Simple GET on /hello/restart (returns success, stop web server,
  121. # measures leaks, restarts webserver)
  122. # - All sockets should get closed
  123. # - Simple GET on /hello/restart_results (returns the leak results)
  124. from __future__ import division, print_function
  125. import argparse
  126. import http.client
  127. import random
  128. import socket
  129. import string
  130. import sys
  131. import threading
  132. import time
  133. from builtins import object, range, str
  134. try:
  135. import Utility
  136. except ImportError:
  137. import os
  138. # This environment variable is expected on the host machine
  139. # > export TEST_FW_PATH=~/esp/esp-idf/tools/tiny-test-fw
  140. test_fw_path = os.getenv('TEST_FW_PATH')
  141. if test_fw_path and test_fw_path not in sys.path:
  142. sys.path.insert(0, test_fw_path)
  143. import Utility
  144. _verbose_ = False
  145. class Session(object):
  146. def __init__(self, addr, port, timeout=15):
  147. self.client = socket.create_connection((addr, int(port)), timeout=timeout)
  148. self.target = addr
  149. self.status = 0
  150. self.encoding = ''
  151. self.content_type = ''
  152. self.content_len = 0
  153. def send_err_check(self, request, data=None):
  154. rval = True
  155. try:
  156. self.client.sendall(request.encode())
  157. if data:
  158. self.client.sendall(data.encode())
  159. except socket.error as err:
  160. self.client.close()
  161. Utility.console_log('Socket Error in send :', err)
  162. rval = False
  163. return rval
  164. def send_get(self, path, headers=None):
  165. request = 'GET ' + path + ' HTTP/1.1\r\nHost: ' + self.target
  166. if headers:
  167. for field, value in headers.items():
  168. request += '\r\n' + field + ': ' + value
  169. request += '\r\n\r\n'
  170. return self.send_err_check(request)
  171. def send_put(self, path, data, headers=None):
  172. request = 'PUT ' + path + ' HTTP/1.1\r\nHost: ' + self.target
  173. if headers:
  174. for field, value in headers.items():
  175. request += '\r\n' + field + ': ' + value
  176. request += '\r\nContent-Length: ' + str(len(data)) + '\r\n\r\n'
  177. return self.send_err_check(request, data)
  178. def send_post(self, path, data, headers=None):
  179. request = 'POST ' + path + ' HTTP/1.1\r\nHost: ' + self.target
  180. if headers:
  181. for field, value in headers.items():
  182. request += '\r\n' + field + ': ' + value
  183. request += '\r\nContent-Length: ' + str(len(data)) + '\r\n\r\n'
  184. return self.send_err_check(request, data)
  185. def read_resp_hdrs(self):
  186. try:
  187. state = 'nothing'
  188. resp_read = ''
  189. while True:
  190. char = self.client.recv(1).decode()
  191. if char == '\r' and state == 'nothing':
  192. state = 'first_cr'
  193. elif char == '\n' and state == 'first_cr':
  194. state = 'first_lf'
  195. elif char == '\r' and state == 'first_lf':
  196. state = 'second_cr'
  197. elif char == '\n' and state == 'second_cr':
  198. state = 'second_lf'
  199. else:
  200. state = 'nothing'
  201. resp_read += char
  202. if state == 'second_lf':
  203. break
  204. # Handle first line
  205. line_hdrs = resp_read.splitlines()
  206. line_comp = line_hdrs[0].split()
  207. self.status = line_comp[1]
  208. del line_hdrs[0]
  209. self.encoding = ''
  210. self.content_type = ''
  211. headers = dict()
  212. # Process other headers
  213. for h in range(len(line_hdrs)):
  214. line_comp = line_hdrs[h].split(':')
  215. if line_comp[0] == 'Content-Length':
  216. self.content_len = int(line_comp[1])
  217. if line_comp[0] == 'Content-Type':
  218. self.content_type = line_comp[1].lstrip()
  219. if line_comp[0] == 'Transfer-Encoding':
  220. self.encoding = line_comp[1].lstrip()
  221. if len(line_comp) == 2:
  222. headers[line_comp[0]] = line_comp[1].lstrip()
  223. return headers
  224. except socket.error as err:
  225. self.client.close()
  226. Utility.console_log('Socket Error in recv :', err)
  227. return None
  228. def read_resp_data(self):
  229. try:
  230. read_data = ''
  231. if self.encoding != 'chunked':
  232. while len(read_data) != self.content_len:
  233. read_data += self.client.recv(self.content_len).decode()
  234. else:
  235. chunk_data_buf = ''
  236. while (True):
  237. # Read one character into temp buffer
  238. read_ch = self.client.recv(1)
  239. # Check CRLF
  240. if (read_ch == '\r'):
  241. read_ch = self.client.recv(1).decode()
  242. if (read_ch == '\n'):
  243. # If CRLF decode length of chunk
  244. chunk_len = int(chunk_data_buf, 16)
  245. # Keep adding to contents
  246. self.content_len += chunk_len
  247. rem_len = chunk_len
  248. while (rem_len):
  249. new_data = self.client.recv(rem_len)
  250. read_data += new_data
  251. rem_len -= len(new_data)
  252. chunk_data_buf = ''
  253. # Fetch remaining CRLF
  254. if self.client.recv(2) != '\r\n':
  255. # Error in packet
  256. Utility.console_log('Error in chunked data')
  257. return None
  258. if not chunk_len:
  259. # If last chunk
  260. break
  261. continue
  262. chunk_data_buf += '\r'
  263. # If not CRLF continue appending
  264. # character to chunked data buffer
  265. chunk_data_buf += read_ch
  266. return read_data
  267. except socket.error as err:
  268. self.client.close()
  269. Utility.console_log('Socket Error in recv :', err)
  270. return None
  271. def close(self):
  272. self.client.close()
  273. def test_val(text, expected, received):
  274. if expected != received:
  275. Utility.console_log(' Fail!')
  276. Utility.console_log(' [reason] ' + text + ':')
  277. Utility.console_log(' expected: ' + str(expected))
  278. Utility.console_log(' received: ' + str(received))
  279. return False
  280. return True
  281. class adder_thread (threading.Thread):
  282. def __init__(self, id, dut, port):
  283. threading.Thread.__init__(self)
  284. self.id = id
  285. self.dut = dut
  286. self.depth = 3
  287. self.session = Session(dut, port)
  288. def run(self):
  289. self.response = []
  290. # Pipeline 3 requests
  291. if (_verbose_):
  292. Utility.console_log(' Thread: Using adder start ' + str(self.id))
  293. for _ in range(self.depth):
  294. self.session.send_post('/adder', str(self.id))
  295. time.sleep(2)
  296. for _ in range(self.depth):
  297. self.session.read_resp_hdrs()
  298. self.response.append(self.session.read_resp_data())
  299. def adder_result(self):
  300. if len(self.response) != self.depth:
  301. Utility.console_log('Error : missing response packets')
  302. return False
  303. for i in range(len(self.response)):
  304. if not test_val('Thread' + str(self.id) + ' response[' + str(i) + ']',
  305. str(self.id * (i + 1)), str(self.response[i])):
  306. return False
  307. return True
  308. def close(self):
  309. self.session.close()
  310. def get_hello(dut, port):
  311. # GET /hello should return 'Hello World!'
  312. Utility.console_log("[test] GET /hello returns 'Hello World!' =>", end=' ')
  313. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  314. conn.request('GET', '/hello')
  315. resp = conn.getresponse()
  316. if not test_val('status_code', 200, resp.status):
  317. conn.close()
  318. return False
  319. if not test_val('data', 'Hello World!', resp.read().decode()):
  320. conn.close()
  321. return False
  322. if not test_val('data', 'text/html', resp.getheader('Content-Type')):
  323. conn.close()
  324. return False
  325. Utility.console_log('Success')
  326. conn.close()
  327. return True
  328. def put_hello(dut, port):
  329. # PUT /hello returns 405'
  330. Utility.console_log('[test] PUT /hello returns 405 =>', end=' ')
  331. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  332. conn.request('PUT', '/hello', 'Hello')
  333. resp = conn.getresponse()
  334. if not test_val('status_code', 405, resp.status):
  335. conn.close()
  336. return False
  337. Utility.console_log('Success')
  338. conn.close()
  339. return True
  340. def post_hello(dut, port):
  341. # POST /hello returns 405'
  342. Utility.console_log('[test] POST /hello returns 405 =>', end=' ')
  343. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  344. conn.request('POST', '/hello', 'Hello')
  345. resp = conn.getresponse()
  346. if not test_val('status_code', 405, resp.status):
  347. conn.close()
  348. return False
  349. Utility.console_log('Success')
  350. conn.close()
  351. return True
  352. def post_echo(dut, port):
  353. # POST /echo echoes data'
  354. Utility.console_log('[test] POST /echo echoes data =>', end=' ')
  355. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  356. conn.request('POST', '/echo', 'Hello')
  357. resp = conn.getresponse()
  358. if not test_val('status_code', 200, resp.status):
  359. conn.close()
  360. return False
  361. if not test_val('data', 'Hello', resp.read().decode()):
  362. conn.close()
  363. return False
  364. Utility.console_log('Success')
  365. conn.close()
  366. return True
  367. def put_echo(dut, port):
  368. # PUT /echo echoes data'
  369. Utility.console_log('[test] PUT /echo echoes data =>', end=' ')
  370. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  371. conn.request('PUT', '/echo', 'Hello')
  372. resp = conn.getresponse()
  373. if not test_val('status_code', 200, resp.status):
  374. conn.close()
  375. return False
  376. if not test_val('data', 'Hello', resp.read().decode()):
  377. conn.close()
  378. return False
  379. Utility.console_log('Success')
  380. conn.close()
  381. return True
  382. def get_echo(dut, port):
  383. # GET /echo returns 404'
  384. Utility.console_log('[test] GET /echo returns 405 =>', end=' ')
  385. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  386. conn.request('GET', '/echo')
  387. resp = conn.getresponse()
  388. if not test_val('status_code', 405, resp.status):
  389. conn.close()
  390. return False
  391. Utility.console_log('Success')
  392. conn.close()
  393. return True
  394. def get_test_headers(dut, port):
  395. # GET /test_header returns data of Header2'
  396. Utility.console_log('[test] GET /test_header =>', end=' ')
  397. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  398. custom_header = {'Header1': 'Value1', 'Header3': 'Value3'}
  399. header2_values = ['', ' ', 'Value2', ' Value2', 'Value2 ', ' Value2 ']
  400. for val in header2_values:
  401. custom_header['Header2'] = val
  402. conn.request('GET', '/test_header', headers=custom_header)
  403. resp = conn.getresponse()
  404. if not test_val('status_code', 200, resp.status):
  405. conn.close()
  406. return False
  407. hdr_val_start_idx = val.find('Value2')
  408. if hdr_val_start_idx == -1:
  409. if not test_val('header: Header2', '', resp.read().decode()):
  410. conn.close()
  411. return False
  412. else:
  413. if not test_val('header: Header2', val[hdr_val_start_idx:], resp.read().decode()):
  414. conn.close()
  415. return False
  416. resp.read()
  417. Utility.console_log('Success')
  418. conn.close()
  419. return True
  420. def get_hello_type(dut, port):
  421. # GET /hello/type_html returns text/html as Content-Type'
  422. Utility.console_log('[test] GET /hello/type_html has Content-Type of text/html =>', end=' ')
  423. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  424. conn.request('GET', '/hello/type_html')
  425. resp = conn.getresponse()
  426. if not test_val('status_code', 200, resp.status):
  427. conn.close()
  428. return False
  429. if not test_val('data', 'Hello World!', resp.read().decode()):
  430. conn.close()
  431. return False
  432. if not test_val('data', 'text/html', resp.getheader('Content-Type')):
  433. conn.close()
  434. return False
  435. Utility.console_log('Success')
  436. conn.close()
  437. return True
  438. def get_hello_status(dut, port):
  439. # GET /hello/status_500 returns status 500'
  440. Utility.console_log('[test] GET /hello/status_500 returns status 500 =>', end=' ')
  441. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  442. conn.request('GET', '/hello/status_500')
  443. resp = conn.getresponse()
  444. if not test_val('status_code', 500, resp.status):
  445. conn.close()
  446. return False
  447. Utility.console_log('Success')
  448. conn.close()
  449. return True
  450. def get_false_uri(dut, port):
  451. # GET /false_uri returns status 404'
  452. Utility.console_log('[test] GET /false_uri returns status 404 =>', end=' ')
  453. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  454. conn.request('GET', '/false_uri')
  455. resp = conn.getresponse()
  456. if not test_val('status_code', 404, resp.status):
  457. conn.close()
  458. return False
  459. Utility.console_log('Success')
  460. conn.close()
  461. return True
  462. def parallel_sessions_adder(dut, port, max_sessions):
  463. # POSTs on /adder in parallel sessions
  464. Utility.console_log('[test] POST {pipelined} on /adder in ' + str(max_sessions) + ' sessions =>', end=' ')
  465. t = []
  466. # Create all sessions
  467. for i in range(max_sessions):
  468. t.append(adder_thread(i, dut, port))
  469. for i in range(len(t)):
  470. t[i].start()
  471. for i in range(len(t)):
  472. t[i].join()
  473. res = True
  474. for i in range(len(t)):
  475. if not test_val('Thread' + str(i) + ' Failed', t[i].adder_result(), True):
  476. res = False
  477. t[i].close()
  478. if (res):
  479. Utility.console_log('Success')
  480. return res
  481. def async_response_test(dut, port):
  482. # Test that an asynchronous work is executed in the HTTPD's context
  483. # This is tested by reading two responses over the same session
  484. Utility.console_log('[test] Test HTTPD Work Queue (Async response) =>', end=' ')
  485. s = Session(dut, port)
  486. s.send_get('/async_data')
  487. s.read_resp_hdrs()
  488. if not test_val('First Response', 'Hello World!', s.read_resp_data()):
  489. s.close()
  490. return False
  491. s.read_resp_hdrs()
  492. if not test_val('Second Response', 'Hello Double World!', s.read_resp_data()):
  493. s.close()
  494. return False
  495. s.close()
  496. Utility.console_log('Success')
  497. return True
  498. def leftover_data_test(dut, port):
  499. # Leftover data in POST is purged (valid and invalid URIs)
  500. Utility.console_log('[test] Leftover data in POST is purged (valid and invalid URIs) =>', end=' ')
  501. s = http.client.HTTPConnection(dut + ':' + port, timeout=15)
  502. s.request('POST', url='/leftover_data', body='abcdefghijklmnopqrstuvwxyz\r\nabcdefghijklmnopqrstuvwxyz')
  503. resp = s.getresponse()
  504. if not test_val('Partial data', 'abcdefghij', resp.read().decode()):
  505. s.close()
  506. return False
  507. s.request('GET', url='/hello')
  508. resp = s.getresponse()
  509. if not test_val('Hello World Data', 'Hello World!', resp.read().decode()):
  510. s.close()
  511. return False
  512. s.request('POST', url='/false_uri', body='abcdefghijklmnopqrstuvwxyz\r\nabcdefghijklmnopqrstuvwxyz')
  513. resp = s.getresponse()
  514. if not test_val('False URI Status', str(404), str(resp.status)):
  515. s.close()
  516. return False
  517. # socket would have been closed by server due to error
  518. s.close()
  519. s = http.client.HTTPConnection(dut + ':' + port, timeout=15)
  520. s.request('GET', url='/hello')
  521. resp = s.getresponse()
  522. if not test_val('Hello World Data', 'Hello World!', resp.read().decode()):
  523. s.close()
  524. return False
  525. s.close()
  526. Utility.console_log('Success')
  527. return True
  528. def spillover_session(dut, port, max_sess):
  529. # Session max_sess_sessions + 1 is rejected
  530. Utility.console_log('[test] Session max_sess_sessions (' + str(max_sess) + ') + 1 is rejected =>', end=' ')
  531. s = []
  532. _verbose_ = True
  533. for i in range(max_sess + 1):
  534. if (_verbose_):
  535. Utility.console_log('Executing ' + str(i))
  536. try:
  537. a = http.client.HTTPConnection(dut + ':' + port, timeout=15)
  538. a.request('GET', url='/hello')
  539. resp = a.getresponse()
  540. if not test_val('Connection ' + str(i), 'Hello World!', resp.read().decode()):
  541. a.close()
  542. break
  543. s.append(a)
  544. except Exception:
  545. if (_verbose_):
  546. Utility.console_log('Connection ' + str(i) + ' rejected')
  547. a.close()
  548. break
  549. # Close open connections
  550. for a in s:
  551. a.close()
  552. # Check if number of connections is equal to max_sess
  553. Utility.console_log(['Fail','Success'][len(s) == max_sess])
  554. return (len(s) == max_sess)
  555. def recv_timeout_test(dut, port):
  556. Utility.console_log('[test] Timeout occurs if partial packet sent =>', end=' ')
  557. s = Session(dut, port)
  558. s.client.sendall(b'GE')
  559. s.read_resp_hdrs()
  560. resp = s.read_resp_data()
  561. if not test_val('Request Timeout', 'Server closed this connection', resp):
  562. s.close()
  563. return False
  564. s.close()
  565. Utility.console_log('Success')
  566. return True
  567. def packet_size_limit_test(dut, port, test_size):
  568. Utility.console_log('[test] send size limit test =>', end=' ')
  569. retry = 5
  570. while (retry):
  571. retry -= 1
  572. Utility.console_log('data size = ', test_size)
  573. s = http.client.HTTPConnection(dut + ':' + port, timeout=15)
  574. random_data = ''.join(string.printable[random.randint(0,len(string.printable)) - 1] for _ in list(range(test_size)))
  575. path = '/echo'
  576. s.request('POST', url=path, body=random_data)
  577. resp = s.getresponse()
  578. if not test_val('Error', '200', str(resp.status)):
  579. if test_val('Error', '500', str(resp.status)):
  580. Utility.console_log('Data too large to be allocated')
  581. test_size = test_size // 10
  582. else:
  583. Utility.console_log('Unexpected error')
  584. s.close()
  585. Utility.console_log('Retry...')
  586. continue
  587. resp = resp.read().decode()
  588. result = (resp == random_data)
  589. if not result:
  590. test_val('Data size', str(len(random_data)), str(len(resp)))
  591. s.close()
  592. Utility.console_log('Retry...')
  593. continue
  594. s.close()
  595. Utility.console_log('Success')
  596. return True
  597. Utility.console_log('Failed')
  598. return False
  599. def arbitrary_termination_test(dut, port):
  600. Utility.console_log('[test] Arbitrary termination test =>', end=' ')
  601. cases = [
  602. {
  603. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nCustom: SomeValue\r\n\r\n',
  604. 'code': '200',
  605. 'header': 'SomeValue'
  606. },
  607. {
  608. 'request': 'POST /echo HTTP/1.1\nHost: ' + dut + '\r\nCustom: SomeValue\r\n\r\n',
  609. 'code': '200',
  610. 'header': 'SomeValue'
  611. },
  612. {
  613. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\nCustom: SomeValue\r\n\r\n',
  614. 'code': '200',
  615. 'header': 'SomeValue'
  616. },
  617. {
  618. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nCustom: SomeValue\n\r\n',
  619. 'code': '200',
  620. 'header': 'SomeValue'
  621. },
  622. {
  623. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nCustom: SomeValue\r\n\n',
  624. 'code': '200',
  625. 'header': 'SomeValue'
  626. },
  627. {
  628. 'request': 'POST /echo HTTP/1.1\nHost: ' + dut + '\nCustom: SomeValue\n\n',
  629. 'code': '200',
  630. 'header': 'SomeValue'
  631. },
  632. {
  633. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nContent-Length: 5\n\r\nABCDE',
  634. 'code': '200',
  635. 'body': 'ABCDE'
  636. },
  637. {
  638. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nContent-Length: 5\r\n\nABCDE',
  639. 'code': '200',
  640. 'body': 'ABCDE'
  641. },
  642. {
  643. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nContent-Length: 5\n\nABCDE',
  644. 'code': '200',
  645. 'body': 'ABCDE'
  646. },
  647. {
  648. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nContent-Length: 5\n\n\rABCD',
  649. 'code': '200',
  650. 'body': '\rABCD'
  651. },
  652. {
  653. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\r\nCustom: SomeValue\r\r\n\r\r\n',
  654. 'code': '400'
  655. },
  656. {
  657. 'request': 'POST /echo HTTP/1.1\r\r\nHost: ' + dut + '\r\n\r\n',
  658. 'code': '400'
  659. },
  660. {
  661. 'request': 'POST /echo HTTP/1.1\r\n\rHost: ' + dut + '\r\n\r\n',
  662. 'code': '400'
  663. },
  664. {
  665. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\rCustom: SomeValue\r\n',
  666. 'code': '400'
  667. },
  668. {
  669. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nCustom: Some\rValue\r\n',
  670. 'code': '400'
  671. },
  672. {
  673. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nCustom- SomeValue\r\n\r\n',
  674. 'code': '400'
  675. }
  676. ]
  677. for case in cases:
  678. s = Session(dut, port)
  679. s.client.sendall((case['request']).encode())
  680. resp_hdrs = s.read_resp_hdrs()
  681. resp_body = s.read_resp_data()
  682. s.close()
  683. if not test_val('Response Code', case['code'], s.status):
  684. return False
  685. if 'header' in case.keys():
  686. resp_hdr_val = None
  687. if 'Custom' in resp_hdrs.keys():
  688. resp_hdr_val = resp_hdrs['Custom']
  689. if not test_val('Response Header', case['header'], resp_hdr_val):
  690. return False
  691. if 'body' in case.keys():
  692. if not test_val('Response Body', case['body'], resp_body):
  693. return False
  694. Utility.console_log('Success')
  695. return True
  696. def code_500_server_error_test(dut, port):
  697. Utility.console_log('[test] 500 Server Error test =>', end=' ')
  698. s = Session(dut, port)
  699. # Sending a very large content length will cause malloc to fail
  700. content_len = 2**30
  701. s.client.sendall(('POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nContent-Length: ' + str(content_len) + '\r\n\r\nABCD').encode())
  702. s.read_resp_hdrs()
  703. s.read_resp_data()
  704. if not test_val('Server Error', '500', s.status):
  705. s.close()
  706. return False
  707. s.close()
  708. Utility.console_log('Success')
  709. return True
  710. def code_501_method_not_impl(dut, port):
  711. Utility.console_log('[test] 501 Method Not Implemented =>', end=' ')
  712. s = Session(dut, port)
  713. path = '/hello'
  714. s.client.sendall(('ABC ' + path + ' HTTP/1.1\r\nHost: ' + dut + '\r\n\r\n').encode())
  715. s.read_resp_hdrs()
  716. s.read_resp_data()
  717. # Presently server sends back 400 Bad Request
  718. # if not test_val("Server Error", "501", s.status):
  719. # s.close()
  720. # return False
  721. if not test_val('Server Error', '400', s.status):
  722. s.close()
  723. return False
  724. s.close()
  725. Utility.console_log('Success')
  726. return True
  727. def code_505_version_not_supported(dut, port):
  728. Utility.console_log('[test] 505 Version Not Supported =>', end=' ')
  729. s = Session(dut, port)
  730. path = '/hello'
  731. s.client.sendall(('GET ' + path + ' HTTP/2.0\r\nHost: ' + dut + '\r\n\r\n').encode())
  732. s.read_resp_hdrs()
  733. s.read_resp_data()
  734. if not test_val('Server Error', '505', s.status):
  735. s.close()
  736. return False
  737. s.close()
  738. Utility.console_log('Success')
  739. return True
  740. def code_400_bad_request(dut, port):
  741. Utility.console_log('[test] 400 Bad Request =>', end=' ')
  742. s = Session(dut, port)
  743. path = '/hello'
  744. s.client.sendall(('XYZ ' + path + ' HTTP/1.1\r\nHost: ' + dut + '\r\n\r\n').encode())
  745. s.read_resp_hdrs()
  746. s.read_resp_data()
  747. if not test_val('Client Error', '400', s.status):
  748. s.close()
  749. return False
  750. s.close()
  751. Utility.console_log('Success')
  752. return True
  753. def code_404_not_found(dut, port):
  754. Utility.console_log('[test] 404 Not Found =>', end=' ')
  755. s = Session(dut, port)
  756. path = '/dummy'
  757. s.client.sendall(('GET ' + path + ' HTTP/1.1\r\nHost: ' + dut + '\r\n\r\n').encode())
  758. s.read_resp_hdrs()
  759. s.read_resp_data()
  760. if not test_val('Client Error', '404', s.status):
  761. s.close()
  762. return False
  763. s.close()
  764. Utility.console_log('Success')
  765. return True
  766. def code_405_method_not_allowed(dut, port):
  767. Utility.console_log('[test] 405 Method Not Allowed =>', end=' ')
  768. s = Session(dut, port)
  769. path = '/hello'
  770. s.client.sendall(('POST ' + path + ' HTTP/1.1\r\nHost: ' + dut + '\r\n\r\n').encode())
  771. s.read_resp_hdrs()
  772. s.read_resp_data()
  773. if not test_val('Client Error', '405', s.status):
  774. s.close()
  775. return False
  776. s.close()
  777. Utility.console_log('Success')
  778. return True
  779. def code_408_req_timeout(dut, port):
  780. Utility.console_log('[test] 408 Request Timeout =>', end=' ')
  781. s = Session(dut, port)
  782. s.client.sendall(('POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nContent-Length: 10\r\n\r\nABCD').encode())
  783. s.read_resp_hdrs()
  784. s.read_resp_data()
  785. if not test_val('Client Error', '408', s.status):
  786. s.close()
  787. return False
  788. s.close()
  789. Utility.console_log('Success')
  790. return True
  791. def code_411_length_required(dut, port):
  792. Utility.console_log('[test] 411 Length Required =>', end=' ')
  793. s = Session(dut, port)
  794. path = '/echo'
  795. s.client.sendall(('POST ' + path + ' HTTP/1.1\r\nHost: ' + dut + '\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n').encode())
  796. s.read_resp_hdrs()
  797. s.read_resp_data()
  798. # Presently server sends back 400 Bad Request
  799. # if not test_val("Client Error", "411", s.status):
  800. # s.close()
  801. # return False
  802. if not test_val('Client Error', '400', s.status):
  803. s.close()
  804. return False
  805. s.close()
  806. Utility.console_log('Success')
  807. return True
  808. def send_getx_uri_len(dut, port, length):
  809. s = Session(dut, port)
  810. method = 'GET '
  811. version = ' HTTP/1.1\r\n'
  812. path = '/' + 'x' * (length - len(method) - len(version) - len('/'))
  813. s.client.sendall(method.encode())
  814. time.sleep(1)
  815. s.client.sendall(path.encode())
  816. time.sleep(1)
  817. s.client.sendall((version + 'Host: ' + dut + '\r\n\r\n').encode())
  818. s.read_resp_hdrs()
  819. s.read_resp_data()
  820. s.close()
  821. return s.status
  822. def code_414_uri_too_long(dut, port, max_uri_len):
  823. Utility.console_log('[test] 414 URI Too Long =>', end=' ')
  824. status = send_getx_uri_len(dut, port, max_uri_len)
  825. if not test_val('Client Error', '404', status):
  826. return False
  827. status = send_getx_uri_len(dut, port, max_uri_len + 1)
  828. if not test_val('Client Error', '414', status):
  829. return False
  830. Utility.console_log('Success')
  831. return True
  832. def send_postx_hdr_len(dut, port, length):
  833. s = Session(dut, port)
  834. path = '/echo'
  835. host = 'Host: ' + dut
  836. custom_hdr_field = '\r\nCustom: '
  837. custom_hdr_val = 'x' * (length - len(host) - len(custom_hdr_field) - len('\r\n\r\n') + len('0'))
  838. request = ('POST ' + path + ' HTTP/1.1\r\n' + host + custom_hdr_field + custom_hdr_val + '\r\n\r\n').encode()
  839. s.client.sendall(request[:length // 2])
  840. time.sleep(1)
  841. s.client.sendall(request[length // 2:])
  842. hdr = s.read_resp_hdrs()
  843. resp = s.read_resp_data()
  844. s.close()
  845. if hdr and ('Custom' in hdr):
  846. return (hdr['Custom'] == custom_hdr_val), resp
  847. return False, s.status
  848. def code_431_hdr_too_long(dut, port, max_hdr_len):
  849. Utility.console_log('[test] 431 Header Too Long =>', end=' ')
  850. res, status = send_postx_hdr_len(dut, port, max_hdr_len)
  851. if not res:
  852. return False
  853. res, status = send_postx_hdr_len(dut, port, max_hdr_len + 1)
  854. if not test_val('Client Error', '431', status):
  855. return False
  856. Utility.console_log('Success')
  857. return True
  858. def test_upgrade_not_supported(dut, port):
  859. Utility.console_log('[test] Upgrade Not Supported =>', end=' ')
  860. s = Session(dut, port)
  861. # path = "/hello"
  862. s.client.sendall(('OPTIONS * HTTP/1.1\r\nHost:' + dut + '\r\nUpgrade: TLS/1.0\r\nConnection: Upgrade\r\n\r\n').encode())
  863. s.read_resp_hdrs()
  864. s.read_resp_data()
  865. if not test_val('Client Error', '400', s.status):
  866. s.close()
  867. return False
  868. s.close()
  869. Utility.console_log('Success')
  870. return True
  871. if __name__ == '__main__':
  872. # Execution begins here...
  873. # Configuration
  874. # Max number of threads/sessions
  875. max_sessions = 7
  876. max_uri_len = 512
  877. max_hdr_len = 512
  878. parser = argparse.ArgumentParser(description='Run HTTPD Test')
  879. parser.add_argument('-4','--ipv4', help='IPv4 address')
  880. parser.add_argument('-6','--ipv6', help='IPv6 address')
  881. parser.add_argument('-p','--port', help='Port')
  882. args = vars(parser.parse_args())
  883. dut4 = args['ipv4']
  884. dut6 = args['ipv6']
  885. port = args['port']
  886. dut = dut4
  887. _verbose_ = True
  888. Utility.console_log('### Basic HTTP Client Tests')
  889. get_hello(dut, port)
  890. post_hello(dut, port)
  891. put_hello(dut, port)
  892. post_echo(dut, port)
  893. get_echo(dut, port)
  894. put_echo(dut, port)
  895. get_hello_type(dut, port)
  896. get_hello_status(dut, port)
  897. get_false_uri(dut, port)
  898. get_test_headers(dut, port)
  899. Utility.console_log('### Error code tests')
  900. code_500_server_error_test(dut, port)
  901. code_501_method_not_impl(dut, port)
  902. code_505_version_not_supported(dut, port)
  903. code_400_bad_request(dut, port)
  904. code_404_not_found(dut, port)
  905. code_405_method_not_allowed(dut, port)
  906. code_408_req_timeout(dut, port)
  907. code_414_uri_too_long(dut, port, max_uri_len)
  908. code_431_hdr_too_long(dut, port, max_hdr_len)
  909. test_upgrade_not_supported(dut, port)
  910. # Not supported yet (Error on chunked request)
  911. # code_411_length_required(dut, port)
  912. Utility.console_log('### Sessions and Context Tests')
  913. parallel_sessions_adder(dut, port, max_sessions)
  914. leftover_data_test(dut, port)
  915. async_response_test(dut, port)
  916. spillover_session(dut, port, max_sessions)
  917. recv_timeout_test(dut, port)
  918. packet_size_limit_test(dut, port, 50 * 1024)
  919. arbitrary_termination_test(dut, port)
  920. get_hello(dut, port)
  921. sys.exit()