1import socket 2 3import pytest 4 5from unit.control import Control 6 7prerequisites = {'modules': {'python': 'any'}} 8 9client = Control() 10 11 12def try_addr(addr): 13 return client.conf( 14 { 15 "listeners": {addr: {"pass": "routes"}}, 16 "routes": [{"action": {"return": 200}}], 17 "applications": {}, 18 } 19 ) 20 21 22def test_json_empty(): 23 assert 'error' in client.conf(''), 'empty' 24 25 26def test_json_leading_zero(): 27 assert 'error' in client.conf('00'), 'leading zero' 28 29 30def test_json_unicode(): 31 assert 'success' in client.conf( 32 """ 33 { 34 "ap\u0070": { 35 "type": "\u0070ython", 36 "processes": { "spare": 0 }, 37 "path": "\u002Fapp", 38 "module": "wsgi" 39 } 40 } 41 """, 42 'applications', 43 ), 'unicode' 44 45 assert client.conf_get('applications') == { 46 "app": { 47 "type": "python", 48 "processes": {"spare": 0}, 49 "path": "/app", 50 "module": "wsgi", 51 } 52 }, 'unicode get' 53 54 55def test_json_unicode_2(): 56 assert 'success' in client.conf( 57 { 58 "приложение": { 59 "type": "python", 60 "processes": {"spare": 0}, 61 "path": "/app", 62 "module": "wsgi", 63 } 64 }, 65 'applications', 66 ), 'unicode 2' 67 68 assert 'приложение' in client.conf_get('applications') 69 70 71def test_json_unicode_number(): 72 assert 'success' in client.conf( 73 """ 74 { 75 "app": { 76 "type": "python", 77 "processes": { "spare": \u0030 }, 78 "path": "/app", 79 "module": "wsgi" 80 } 81 } 82 """, 83 'applications', 84 ), 'unicode number' 85 86 87def test_json_utf8_bom(): 88 assert 'success' in client.conf( 89 b"""\xEF\xBB\xBF 90 { 91 "app": { 92 "type": "python", 93 "processes": {"spare": 0}, 94 "path": "/app", 95 "module": "wsgi" 96 } 97 } 98 """, 99 'applications', 100 ), 'UTF-8 BOM' 101 102 103def test_json_comment_single_line(): 104 assert 'success' in client.conf( 105 b""" 106 // this is bridge 107 { 108 "//app": { 109 "type": "python", // end line 110 "processes": {"spare": 0}, 111 // inside of block 112 "path": "/app", 113 "module": "wsgi" 114 } 115 // double // 116 } 117 // end of json \xEF\t 118 """, 119 'applications', 120 ), 'single line comments' 121 122 123def test_json_comment_multi_line(): 124 assert 'success' in client.conf( 125 b""" 126 /* this is bridge */ 127 { 128 "/*app": { 129 /** 130 * multiple lines 131 **/ 132 "type": "python", 133 "processes": /* inline */ {"spare": 0}, 134 "path": "/app", 135 "module": "wsgi" 136 /* 137 // end of block */ 138 } 139 /* blah * / blah /* blah */ 140 } 141 /* end of json \xEF\t\b */ 142 """, 143 'applications', 144 ), 'multi line comments' 145 146 147def test_json_comment_invalid(): 148 assert 'error' in client.conf(b'/{}', 'applications'), 'slash' 149 assert 'error' in client.conf(b'//{}', 'applications'), 'comment' 150 assert 'error' in client.conf(b'{} /', 'applications'), 'slash end' 151 assert 'error' in client.conf(b'/*{}', 'applications'), 'slash star' 152 assert 'error' in client.conf(b'{} /*', 'applications'), 'slash star end' 153 154 155def test_applications_open_brace(): 156 assert 'error' in client.conf('{', 'applications'), 'open brace' 157 158 159def test_applications_string(): 160 assert 'error' in client.conf('"{}"', 'applications'), 'string' 161 162 163@pytest.mark.skip('not yet, unsafe') 164def test_applications_type_only(): 165 assert 'error' in client.conf( 166 {"app": {"type": "python"}}, 'applications' 167 ), 'type only' 168 169 170def test_applications_miss_quote(): 171 assert 'error' in client.conf( 172 """ 173 { 174 app": { 175 "type": "python", 176 "processes": { "spare": 0 }, 177 "path": "/app", 178 "module": "wsgi" 179 } 180 } 181 """, 182 'applications', 183 ), 'miss quote' 184 185 186def test_applications_miss_colon(): 187 assert 'error' in client.conf( 188 """ 189 { 190 "app" { 191 "type": "python", 192 "processes": { "spare": 0 }, 193 "path": "/app", 194 "module": "wsgi" 195 } 196 } 197 """, 198 'applications', 199 ), 'miss colon' 200 201 202def test_applications_miss_comma(): 203 assert 'error' in client.conf( 204 """ 205 { 206 "app": { 207 "type": "python" 208 "processes": { "spare": 0 }, 209 "path": "/app", 210 "module": "wsgi" 211 } 212 } 213 """, 214 'applications', 215 ), 'miss comma' 216 217 218def test_applications_skip_spaces(): 219 assert 'success' in client.conf(b'{ \n\r\t}', 'applications'), 'skip spaces' 220 221 222def test_applications_relative_path(): 223 assert 'success' in client.conf( 224 { 225 "app": { 226 "type": "python", 227 "processes": {"spare": 0}, 228 "path": "../app", 229 "module": "wsgi", 230 } 231 }, 232 'applications', 233 ), 'relative path' 234 235 236@pytest.mark.skip('not yet, unsafe') 237def test_listeners_empty(): 238 assert 'error' in client.conf({"*:8080": {}}, 'listeners'), 'listener empty' 239 240 241def test_listeners_no_app(): 242 assert 'error' in client.conf( 243 {"*:8080": {"pass": "applications/app"}}, 'listeners' 244 ), 'listeners no app' 245 246 247def test_listeners_unix_abstract(system): 248 if system != 'Linux': 249 assert 'error' in try_addr("unix:@sock"), 'abstract at' 250 251 pytest.skip('not yet') 252 253 assert 'error' in try_addr("unix:\0soc"), 'abstract \0' 254 assert 'error' in try_addr("unix:\u0000soc"), 'abstract \0 unicode' 255 256 257def test_listeners_addr(): 258 assert 'success' in try_addr("*:8080"), 'wildcard' 259 assert 'success' in try_addr("127.0.0.1:8081"), 'explicit' 260 assert 'success' in try_addr("[::1]:8082"), 'explicit ipv6' 261 262 263def test_listeners_addr_error(): 264 assert 'error' in try_addr("127.0.0.1"), 'no port' 265 266 267def test_listeners_addr_error_2(skip_alert): 268 skip_alert(r'bind.*failed', r'failed to apply new conf') 269 270 assert 'error' in try_addr("[f607:7403:1e4b:6c66:33b2:843f:2517:da27]:8080") 271 272 273def test_listeners_port_release(): 274 for _ in range(10): 275 fail = False 276 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: 277 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 278 279 client.conf( 280 { 281 "listeners": {"127.0.0.1:8080": {"pass": "routes"}}, 282 "routes": [], 283 } 284 ) 285 286 resp = client.conf({"listeners": {}, "applications": {}}) 287 288 try: 289 s.bind(('127.0.0.1', 8080)) 290 s.listen() 291 292 except OSError: 293 fail = True 294 295 if fail: 296 pytest.fail('cannot bind or listen to the address') 297 298 assert 'success' in resp, 'port release' 299 300 301def test_json_application_name_large(): 302 name = "X" * 1024 * 1024 303 304 assert 'success' in client.conf( 305 { 306 "listeners": {"*:8080": {"pass": f"applications/{name}"}}, 307 "applications": { 308 name: { 309 "type": "python", 310 "processes": {"spare": 0}, 311 "path": "/app", 312 "module": "wsgi", 313 } 314 }, 315 } 316 ) 317 318 319@pytest.mark.skip('not yet') 320def test_json_application_many(): 321 apps = 999 322 323 conf = { 324 "applications": { 325 f"app-{a}": { 326 "type": "python", 327 "processes": {"spare": 0}, 328 "path": "/app", 329 "module": "wsgi", 330 } 331 for a in range(apps) 332 }, 333 "listeners": { 334 f"*:{(7000 + a)}": {"pass": f"applications/app-{a}"} 335 for a in range(apps) 336 }, 337 } 338 339 assert 'success' in client.conf(conf) 340 341 342def test_json_application_python_prefix(): 343 conf = { 344 "applications": { 345 "sub-app": { 346 "type": "python", 347 "processes": {"spare": 0}, 348 "path": "/app", 349 "module": "wsgi", 350 "prefix": "/app", 351 } 352 }, 353 "listeners": {"*:8080": {"pass": "routes"}}, 354 "routes": [ 355 { 356 "match": {"uri": "/app/*"}, 357 "action": {"pass": "applications/sub-app"}, 358 } 359 ], 360 } 361 362 assert 'success' in client.conf(conf) 363 364 365def test_json_application_prefix_target(): 366 conf = { 367 "applications": { 368 "sub-app": { 369 "type": "python", 370 "processes": {"spare": 0}, 371 "path": "/app", 372 "targets": { 373 "foo": {"module": "foo.wsgi", "prefix": "/app"}, 374 "bar": { 375 "module": "bar.wsgi", 376 "callable": "bar", 377 "prefix": "/api", 378 }, 379 }, 380 } 381 }, 382 "listeners": {"*:8080": {"pass": "routes"}}, 383 "routes": [ 384 { 385 "match": {"uri": "/app/*"}, 386 "action": {"pass": "applications/sub-app/foo"}, 387 }, 388 { 389 "match": {"uri": "/api/*"}, 390 "action": {"pass": "applications/sub-app/bar"}, 391 }, 392 ], 393 } 394 395 assert 'success' in client.conf(conf) 396 397 398def test_json_application_invalid_python_prefix(): 399 conf = { 400 "applications": { 401 "sub-app": { 402 "type": "python", 403 "processes": {"spare": 0}, 404 "path": "/app", 405 "module": "wsgi", 406 "prefix": "app", 407 } 408 }, 409 "listeners": {"*:8080": {"pass": "applications/sub-app"}}, 410 } 411 412 assert 'error' in client.conf(conf) 413 414 415def test_json_application_empty_python_prefix(): 416 conf = { 417 "applications": { 418 "sub-app": { 419 "type": "python", 420 "processes": {"spare": 0}, 421 "path": "/app", 422 "module": "wsgi", 423 "prefix": "", 424 } 425 }, 426 "listeners": {"*:8080": {"pass": "applications/sub-app"}}, 427 } 428 429 assert 'error' in client.conf(conf) 430 431 432def test_json_application_many2(): 433 conf = { 434 "applications": { 435 f"app-{a}": { 436 "type": "python", 437 "processes": {"spare": 0}, 438 "path": "/app", 439 "module": "wsgi", 440 } 441 # Larger number of applications can cause test fail with default 442 # open files limit due to the lack of file descriptors. 443 for a in range(100) 444 }, 445 "listeners": {"*:8080": {"pass": "applications/app-1"}}, 446 } 447 448 assert 'success' in client.conf(conf) 449 450 451def test_unprivileged_user_error(require, skip_alert): 452 require({'privileged_user': False}) 453 454 skip_alert(r'cannot set user "root"', r'failed to apply new conf') 455 456 assert 'error' in client.conf( 457 { 458 "app": { 459 "type": "external", 460 "processes": 1, 461 "executable": "/app", 462 "user": "root", 463 } 464 }, 465 'applications', 466 ), 'setting user' 467