1# Copyright 2023 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5from __future__ import annotations 6 7import argparse 8import datetime as dt 9import json 10import pathlib 11import unittest 12from typing import Any 13from urllib import parse as urlparse 14 15from crossbench.parse import (DurationParser, NumberParser, ObjectParser, 16 PathParser) 17from tests import test_helper 18from tests.crossbench.base import CrossbenchFakeFsTestCase 19 20 21class DurationParserTestCase(unittest.TestCase): 22 23 def test_parse_negative(self): 24 with self.assertRaises(argparse.ArgumentTypeError): 25 DurationParser.positive_duration(-1) 26 with self.assertRaises(argparse.ArgumentTypeError) as cm: 27 DurationParser.positive_duration("-1") 28 with self.assertRaises(argparse.ArgumentTypeError) as cm: 29 DurationParser.positive_or_zero_duration("-1") 30 with self.assertRaises(argparse.ArgumentTypeError) as cm: 31 DurationParser.positive_or_zero_duration(dt.timedelta(seconds=-1)) 32 with self.assertRaises(argparse.ArgumentTypeError) as cm: 33 DurationParser.positive_duration("-1") 34 with self.assertRaises(argparse.ArgumentTypeError) as cm: 35 DurationParser.positive_duration(dt.timedelta(seconds=-1)) 36 self.assertIn("-1", str(cm.exception)) 37 self.assertEqual(DurationParser.any_duration("-1.5").total_seconds(), -1.5) 38 39 def test_parse_zero(self): 40 self.assertEqual(DurationParser.any_duration("0").total_seconds(), 0) 41 self.assertEqual(DurationParser.any_duration("0s").total_seconds(), 0) 42 self.assertEqual(DurationParser.any_duration("0.0").total_seconds(), 0) 43 self.assertEqual( 44 DurationParser.positive_or_zero_duration("0.0").total_seconds(), 0) 45 for invalid in (-1, 0, "-1", "0", "invalid", dt.timedelta(0), 46 dt.timedelta(seconds=-1)): 47 with self.assertRaises(argparse.ArgumentTypeError) as cm: 48 DurationParser.positive_duration(invalid) 49 self.assertIn(str(invalid), str(cm.exception)) 50 with self.assertRaises(argparse.ArgumentTypeError) as cm: 51 DurationParser.positive_duration(invalid) 52 self.assertIn(str(invalid), str(cm.exception)) 53 54 def test_parse_empty(self): 55 with self.assertRaises(argparse.ArgumentTypeError): 56 DurationParser.positive_duration("") 57 with self.assertRaises(argparse.ArgumentTypeError): 58 DurationParser.any_duration("") 59 with self.assertRaises(argparse.ArgumentTypeError): 60 DurationParser.positive_or_zero_duration("") 61 with self.assertRaises(argparse.ArgumentTypeError): 62 DurationParser.positive_duration("") 63 64 def test_invalid_suffix(self): 65 with self.assertRaises(argparse.ArgumentTypeError) as cm: 66 DurationParser.positive_duration("100XXX") 67 self.assertIn("Unknown duration format", str(cm.exception)) 68 with self.assertRaises(argparse.ArgumentTypeError): 69 DurationParser.positive_duration("X0XX") 70 with self.assertRaises(argparse.ArgumentTypeError): 71 DurationParser.positive_duration("100X0XX") 72 73 def test_no_unit(self): 74 self.assertEqual( 75 DurationParser.positive_duration("200"), dt.timedelta(seconds=200)) 76 self.assertEqual( 77 DurationParser.positive_duration(200), dt.timedelta(seconds=200)) 78 79 def test_milliseconds(self): 80 self.assertEqual( 81 DurationParser.positive_duration("27.5ms"), 82 dt.timedelta(milliseconds=27.5)) 83 self.assertEqual( 84 DurationParser.positive_duration(dt.timedelta(milliseconds=27.5)), 85 dt.timedelta(milliseconds=27.5)) 86 self.assertEqual( 87 DurationParser.positive_duration("27.5 millis"), 88 dt.timedelta(milliseconds=27.5)) 89 self.assertEqual( 90 DurationParser.positive_duration("27.5 milliseconds"), 91 dt.timedelta(milliseconds=27.5)) 92 93 def test_seconds(self): 94 self.assertEqual( 95 DurationParser.positive_duration("27.5s"), dt.timedelta(seconds=27.5)) 96 self.assertEqual( 97 DurationParser.positive_duration("1 sec"), dt.timedelta(seconds=1)) 98 self.assertEqual( 99 DurationParser.positive_duration("27.5 secs"), 100 dt.timedelta(seconds=27.5)) 101 self.assertEqual( 102 DurationParser.positive_duration("1 second"), dt.timedelta(seconds=1)) 103 self.assertEqual( 104 DurationParser.positive_duration("27.5 seconds"), 105 dt.timedelta(seconds=27.5)) 106 107 def test_minutes(self): 108 self.assertEqual( 109 DurationParser.positive_duration("27.5m"), dt.timedelta(minutes=27.5)) 110 self.assertEqual( 111 DurationParser.positive_duration("1 min"), dt.timedelta(minutes=1)) 112 self.assertEqual( 113 DurationParser.positive_duration("27.5 mins"), 114 dt.timedelta(minutes=27.5)) 115 self.assertEqual( 116 DurationParser.positive_duration("1 minute"), dt.timedelta(minutes=1)) 117 self.assertEqual( 118 DurationParser.positive_duration("27.5 minutes"), 119 dt.timedelta(minutes=27.5)) 120 121 def test_hours(self): 122 self.assertEqual( 123 DurationParser.positive_duration("27.5h"), dt.timedelta(hours=27.5)) 124 self.assertEqual( 125 DurationParser.positive_duration("0.1 h"), dt.timedelta(hours=0.1)) 126 self.assertEqual( 127 DurationParser.positive_duration("27.5 hrs"), dt.timedelta(hours=27.5)) 128 self.assertEqual( 129 DurationParser.positive_duration("1 hour"), dt.timedelta(hours=1)) 130 self.assertEqual( 131 DurationParser.positive_duration("27.5 hours"), 132 dt.timedelta(hours=27.5)) 133 134 135class ObjectParserHelperTestCase(CrossbenchFakeFsTestCase): 136 137 def setUp(self): 138 super().setUp() 139 self._json_test_data = {"int": 1, "array": [1, "2"]} 140 141 def test_parse_any_str(self): 142 self.assertEqual(ObjectParser.any_str(""), "") 143 self.assertEqual(ObjectParser.any_str("1234"), "1234") 144 145 def test_parse_any_str_invalid(self): 146 for invalid in (None, 1, [], {}, [1], ["a"], {"a": "a"}): 147 with self.assertRaises(argparse.ArgumentTypeError) as cm: 148 ObjectParser.any_str(invalid) 149 self.assertIn(str(invalid), str(cm.exception)) 150 151 def test_parse_non_empty_str(self): 152 self.assertEqual(ObjectParser.non_empty_str("a string"), "a string") 153 with self.assertRaises(argparse.ArgumentTypeError) as cm: 154 ObjectParser.non_empty_str("") 155 self.assertIn("empty", str(cm.exception)) 156 157 def test_parse_httpx_url_str(self): 158 for valid in ("http://foo.com", "https://foo.com", "http://localhost:800"): 159 self.assertEqual(ObjectParser.httpx_url_str(valid), valid) 160 for invalid in ("", "ftp://localhost:32", "http://///"): 161 with self.assertRaises(argparse.ArgumentTypeError) as cm: 162 _ = ObjectParser.httpx_url_str(invalid) 163 self.assertIn(invalid, str(cm.exception)) 164 165 def test_parse_any_int(self): 166 self.assertEqual(NumberParser.any_int("-123456"), -123456) 167 self.assertEqual(NumberParser.any_int(-123456), -123456) 168 self.assertEqual(NumberParser.any_int("-1"), -1) 169 self.assertEqual(NumberParser.any_int(-1), -1) 170 self.assertEqual(NumberParser.any_int("0"), 0) 171 self.assertEqual(NumberParser.any_int(0), 0) 172 self.assertEqual(NumberParser.any_int("1"), 1) 173 self.assertEqual(NumberParser.any_int(1), 1) 174 self.assertEqual(NumberParser.any_int("123456"), 123456) 175 self.assertEqual(NumberParser.any_int(123456), 123456) 176 177 def test_parse_any_int_invalid(self): 178 for invalid in ("", "-1.2", "1.2", "100.001", "Nan", "inf", "-inf", 179 "invalid"): 180 with self.assertRaises(argparse.ArgumentTypeError): 181 _ = NumberParser.any_int(invalid) 182 183 def test_parse_positive_int(self): 184 self.assertEqual(NumberParser.positive_int("1"), 1) 185 self.assertEqual(NumberParser.positive_int("123"), 123) 186 187 def test_parse_positive_int_ivalid(self): 188 for invalid in ("", "0", "-1", "-1.2", "1.2", "Nan", "inf", "-inf", 189 "invalid"): 190 with self.assertRaises(argparse.ArgumentTypeError): 191 _ = NumberParser.positive_int(invalid) 192 193 def test_parse_positive_zero_int(self): 194 self.assertEqual(NumberParser.positive_zero_int("1"), 1) 195 self.assertEqual(NumberParser.positive_zero_int("0"), 0) 196 197 def test_parse_positive_zero_int_invalid(self): 198 for invalid in ("", "-1", "-1.2", "1.2", "NaN", "inf", "-inf", "invalid"): 199 with self.assertRaises(argparse.ArgumentTypeError): 200 _ = NumberParser.positive_zero_int(invalid) 201 202 def test_parse_any_float(self): 203 self.assertEqual(NumberParser.any_float("-1.2"), -1.2) 204 self.assertEqual(NumberParser.any_float(-1.2), -1.2) 205 self.assertEqual(NumberParser.any_float("-1"), -1.0) 206 self.assertEqual(NumberParser.any_float(-1), -1.0) 207 self.assertEqual(NumberParser.any_float("0"), 0.0) 208 self.assertEqual(NumberParser.any_float(0), 0.0) 209 self.assertEqual(NumberParser.any_float("0.0"), 0.0) 210 self.assertEqual(NumberParser.any_float(0.0), 0.0) 211 self.assertEqual(NumberParser.any_float("0.1"), 0.1) 212 self.assertEqual(NumberParser.any_float(0.1), 0.1) 213 214 def test_parse_float_invalid(self): 215 for invalid in ("", "abc", "NaN", "inf", "-inf", "invalid"): 216 with self.assertRaises(argparse.ArgumentTypeError): 217 _ = NumberParser.positive_zero_float(invalid) 218 219 def test_parse_positive_zero_float(self): 220 self.assertEqual(NumberParser.positive_zero_float("1"), 1.0) 221 self.assertEqual(NumberParser.positive_zero_float("0"), 0.0) 222 self.assertEqual(NumberParser.positive_zero_float("0.0"), 0.0) 223 self.assertEqual(NumberParser.positive_zero_float("1.23"), 1.23) 224 225 def test_parse_positive_zero_float_invlid(self): 226 for invalid in ("", "-1", "-1.2", "NaN", "inf", "-inf", "invalid"): 227 with self.assertRaises(argparse.ArgumentTypeError): 228 _ = NumberParser.positive_zero_float(invalid) 229 230 def test_parse_port_number(self): 231 self.assertEqual(NumberParser.port_number(1), 1) 232 self.assertEqual(NumberParser.port_number("1"), 1) 233 self.assertEqual(NumberParser.port_number(440), 440) 234 self.assertEqual(NumberParser.port_number("440"), 440) 235 self.assertEqual(NumberParser.port_number(65535), 65535) 236 self.assertEqual(NumberParser.port_number("65535"), 65535) 237 238 def test_parse_port_number_invalid(self): 239 for invalid in ("", "-1", "-1.2", "6553500", "inf", "-inf", "invalid"): 240 with self.assertRaises(argparse.ArgumentTypeError): 241 _ = NumberParser.port_number(invalid) 242 243 def _json_file_test_helper(self, parser) -> Any: 244 with self.assertRaises(argparse.ArgumentTypeError): 245 parser("file") 246 247 path = pathlib.Path("file.json") 248 self.assertFalse(path.exists()) 249 with self.assertRaises(argparse.ArgumentTypeError): 250 parser(path) 251 252 path.touch() 253 with self.assertRaises(argparse.ArgumentTypeError): 254 parser(path) 255 256 with path.open("w", encoding="utf-8") as f: 257 f.write("{invalid json data") 258 with self.assertRaises(argparse.ArgumentTypeError): 259 parser(path) 260 # Test very long lines too. 261 with path.open("w", encoding="utf-8") as f: 262 f.write("{\n invalid json data" + "." * 100) 263 with self.assertRaises(argparse.ArgumentTypeError): 264 parser(path) 265 266 with path.open("w", encoding="utf-8") as f: 267 f.write("""{ 268 'a': {}, 269 'c': }} 270 """) 271 with self.assertRaises(argparse.ArgumentTypeError): 272 parser(path) 273 274 with path.open("w", encoding="utf-8") as f: 275 json.dump(self._json_test_data, f) 276 str_result = parser(str(path)) 277 path_result = parser(path) 278 self.assertEqual(str_result, path_result) 279 return str_result 280 281 def test_parse_json_file(self): 282 result = self._json_file_test_helper(ObjectParser.json_file) 283 self.assertDictEqual(self._json_test_data, result) 284 285 def test_parse_json_file_path(self): 286 result = self._json_file_test_helper(PathParser.json_file_path) 287 self.assertEqual(pathlib.Path("file.json"), result) 288 289 def test_parse_hjson_file_path(self): 290 result = self._json_file_test_helper(PathParser.hjson_file_path) 291 self.assertEqual(pathlib.Path("file.json"), result) 292 293 def test_parse_inline_hjson(self): 294 with self.assertRaises(argparse.ArgumentTypeError): 295 ObjectParser.inline_hjson("") 296 with self.assertRaises(argparse.ArgumentTypeError): 297 ObjectParser.inline_hjson("{invalid json}") 298 with self.assertRaises(argparse.ArgumentTypeError): 299 ObjectParser.inline_hjson("{'asdfas':'asdf}") 300 self.assertDictEqual( 301 self._json_test_data, 302 ObjectParser.inline_hjson(json.dumps(self._json_test_data))) 303 304 def test_parse_dir_path(self): 305 with self.assertRaises(argparse.ArgumentTypeError): 306 PathParser.dir_path("") 307 file = pathlib.Path("file") 308 with self.assertRaises(argparse.ArgumentTypeError) as cm: 309 PathParser.dir_path(file) 310 self.assertIn("does not exist", str(cm.exception)) 311 file.touch() 312 with self.assertRaises(argparse.ArgumentTypeError) as cm: 313 PathParser.dir_path(file) 314 self.assertIn("not a folder", str(cm.exception)) 315 folder = pathlib.Path("folder") 316 folder.mkdir() 317 self.assertEqual(folder, PathParser.dir_path(folder)) 318 self.assertEqual(folder, PathParser.dir_path(str(folder))) 319 320 def test_parse_non_empty_dir_path(self): 321 folder = pathlib.Path("folder") 322 folder.mkdir() 323 with self.assertRaises(argparse.ArgumentTypeError) as cm: 324 PathParser.non_empty_dir_path(folder) 325 self.assertIn("empty", str(cm.exception)) 326 (folder / "foo").touch() 327 self.assertEqual(folder, PathParser.non_empty_dir_path(folder)) 328 self.assertEqual(folder, PathParser.non_empty_dir_path(str(folder))) 329 330 def test_parse_non_empty_file_path(self): 331 with self.assertRaises(argparse.ArgumentTypeError): 332 PathParser.non_empty_file_path("") 333 folder = pathlib.Path("folder") 334 with self.assertRaises(argparse.ArgumentTypeError) as cm: 335 PathParser.non_empty_file_path(folder) 336 self.assertIn("does not exist", str(cm.exception)) 337 folder.mkdir() 338 with self.assertRaises(argparse.ArgumentTypeError) as cm: 339 PathParser.non_empty_file_path(folder) 340 self.assertIn("not a file", str(cm.exception)) 341 file = pathlib.Path("file") 342 file.touch() 343 with self.assertRaises(argparse.ArgumentTypeError) as cm: 344 self.assertEqual(file, PathParser.non_empty_file_path(file)) 345 self.assertIn("is an empty file", str(cm.exception)) 346 347 with file.open("w", encoding="utf-8") as f: 348 f.write("fooo") 349 self.assertEqual(file, PathParser.non_empty_file_path(file)) 350 351 def test_parse_existing_file_path(self): 352 with self.assertRaises(argparse.ArgumentTypeError): 353 PathParser.existing_file_path("") 354 folder = pathlib.Path("folder") 355 with self.assertRaises(argparse.ArgumentTypeError) as cm: 356 PathParser.existing_file_path(folder) 357 self.assertIn("does not exist", str(cm.exception)) 358 folder.mkdir() 359 with self.assertRaises(argparse.ArgumentTypeError) as cm: 360 PathParser.existing_file_path(folder) 361 self.assertIn("not a file", str(cm.exception)) 362 file = pathlib.Path("file") 363 file.touch() 364 self.assertEqual(file, PathParser.existing_file_path(file)) 365 366 def test_parse_path(self): 367 with self.assertRaises(argparse.ArgumentTypeError): 368 PathParser.path("") 369 folder = pathlib.Path("folder") 370 folder.mkdir() 371 self.assertEqual(folder, PathParser.path(folder)) 372 file = pathlib.Path("file") 373 file.touch() 374 self.assertEqual(file, PathParser.path(file)) 375 376 def test_parse_bool_success(self): 377 self.assertIs(ObjectParser.bool("true"), True) 378 self.assertIs(ObjectParser.bool("True"), True) 379 self.assertIs(ObjectParser.bool(True), True) 380 self.assertIs(ObjectParser.bool("false"), False) 381 self.assertIs(ObjectParser.bool("False"), False) 382 self.assertIs(ObjectParser.bool(False), False) 383 384 def test_parse_bool_invalid(self): 385 for invalid in (1, 0, "1", "0", "", None, [], tuple()): 386 with self.assertRaises(argparse.ArgumentTypeError): 387 ObjectParser.bool(invalid) 388 389 def test_parse_sh_cmd(self): 390 self.assertListEqual(ObjectParser.sh_cmd("ls -al ."), ["ls", "-al", "."]) 391 self.assertListEqual(ObjectParser.sh_cmd("ls -al '.'"), ["ls", "-al", "."]) 392 self.assertListEqual( 393 ObjectParser.sh_cmd(";ls -al '.'"), [";ls", "-al", "."]) 394 self.assertListEqual( 395 ObjectParser.sh_cmd(("ls", "-al", ".")), ["ls", "-al", "."]) 396 397 def test_parse_sh_cmd_invalid(self): 398 for invalid in (1, "", None, [], "ls -al \"."): 399 with self.assertRaises(argparse.ArgumentTypeError): 400 ObjectParser.sh_cmd(invalid) 401 402 def test_parse_dict_invalid(self): 403 for invalid in (1, 0, "1", "0", "", None, [], tuple()): 404 with self.assertRaises(argparse.ArgumentTypeError): 405 ObjectParser.dict(invalid) 406 407 def test_parse_dict(self): 408 self.assertDictEqual(ObjectParser.dict({}), {}) 409 self.assertDictEqual(ObjectParser.dict({"A": 2}), {"A": 2}) 410 411 def test_parse_non_empty_dict_invalid(self): 412 for invalid in (1, 0, "1", "0", "", None, [], tuple(), {}): 413 with self.assertRaises(argparse.ArgumentTypeError): 414 ObjectParser.non_empty_dict(invalid) 415 416 def test_parse_non_empty_dict(self): 417 result = ObjectParser.non_empty_dict({"a": 1}) 418 self.assertDictEqual(result, {"a": 1}) 419 420 def test_parse_unique_sequence(self): 421 self.assertListEqual(ObjectParser.unique_sequence([]), []) 422 self.assertTupleEqual(ObjectParser.unique_sequence(tuple()), tuple()) 423 self.assertListEqual(ObjectParser.unique_sequence([1, 2, 3]), [1, 2, 3]) 424 self.assertTupleEqual(ObjectParser.unique_sequence((1, 2, 3)), (1, 2, 3)) 425 426 def test_parse_unique_sequence_invalid(self): 427 with self.assertRaises(argparse.ArgumentTypeError) as cm: 428 ObjectParser.unique_sequence([1, 1, 2, 2, 2, 3, 5, 5]) 429 self.assertIn("duplicates", str(cm.exception)) 430 self.assertIn("1, 2, 5", str(cm.exception)) 431 432 def test_parse_unique_sequence_custom_exception(self): 433 434 class CustomException(Exception): 435 pass 436 437 with self.assertRaises(CustomException): 438 ObjectParser.unique_sequence([1, 1], error_cls=CustomException) 439 440 def test_parse_unique_sequence_custom_name(self): 441 with self.assertRaises(argparse.ArgumentTypeError) as cm: 442 ObjectParser.unique_sequence([1, 1], name="custom test name") 443 self.assertIn("custom test name", str(cm.exception)) 444 445 def test_parse_sequence(self): 446 self.assertListEqual(ObjectParser.sequence([]), []) 447 self.assertListEqual(ObjectParser.sequence([1, 2]), [1, 2]) 448 self.assertTupleEqual(ObjectParser.sequence(tuple()), tuple()) 449 self.assertTupleEqual(ObjectParser.sequence((1, 2)), (1, 2)) 450 451 def test_parse_sequence_invalid(self): 452 for invalid in ("", "1", 1, {}, {"a": 1}, set(), set((1, 2))): 453 with self.subTest(invalid=invalid): 454 with self.assertRaises(argparse.ArgumentTypeError): 455 ObjectParser.sequence(invalid) 456 457 def test_parse_non_empty_sequence(self): 458 with self.assertRaises(argparse.ArgumentTypeError): 459 _ = ObjectParser.non_empty_sequence([]) 460 self.assertListEqual(ObjectParser.non_empty_sequence([1, 2]), [1, 2]) 461 with self.assertRaises(argparse.ArgumentTypeError): 462 _ = ObjectParser.non_empty_sequence(tuple()) 463 self.assertTupleEqual(ObjectParser.non_empty_sequence((1, 2)), (1, 2)) 464 465 def test_parse_non_empty_sequence_invalid(self): 466 for invalid in ("", "1", 1, {}, {"a": 1}, set(), set((1, 2)), (), []): 467 with self.subTest(invalid=invalid): 468 with self.assertRaises(argparse.ArgumentTypeError): 469 ObjectParser.non_empty_sequence(invalid) 470 471 def test_parse_fuzzy_url(self): 472 expected = ( 473 ("/foo/bar", "file:///foo/bar"), 474 ("C:/foo/bar", "file://C:/foo/bar"), 475 ("1234.com", "https://1234.com"), 476 ("http://1234.com", "http://1234.com"), 477 ("test.com", "https://test.com"), 478 ("test.com/", "https://test.com/"), 479 ("test.com/1234", "https://test.com/1234"), 480 ("test.com/bar", "https://test.com/bar"), 481 ("test.com/bar?x=1", "https://test.com/bar?x=1"), 482 ("test.com:1234", "https://test.com:1234"), 483 ("test.com:1234/", "https://test.com:1234/"), 484 ("test.com:1234/56", "https://test.com:1234/56"), 485 ("test.com:1234/56/", "https://test.com:1234/56/"), 486 ("test.com:1234/bar", "https://test.com:1234/bar"), 487 ("test.com:1234/bar?x=1", "https://test.com:1234/bar?x=1"), 488 ("localhost:8123", "https://localhost:8123"), 489 ("localhost:8123/", "https://localhost:8123/"), 490 ("localhost:8123/77", "https://localhost:8123/77"), 491 ("localhost:8123/77/", "https://localhost:8123/77/"), 492 ("localhost:8123/bar", "https://localhost:8123/bar"), 493 ("localhost:8123/bar?x=1", "https://localhost:8123/bar?x=1"), 494 ) 495 for url, result in expected: 496 with self.subTest(url=url): 497 self.assertEqual(ObjectParser.parse_fuzzy_url_str(url), result) 498 parsed = ObjectParser.parse_fuzzy_url(url) 499 self.assertEqual(urlparse.urlunparse(parsed), result) 500 501 def test_parse_fuzzy_url_default_scheme(self): 502 expected = ("test.com", "test.com/", "test.com/bar", "test.com/bar?x=1", 503 "test.com:1234", "test.com:1234/", "test.com:1234/bar", 504 "test.com:1234/bar?x=1", "localhost:8123", "localhost:8123/", 505 "localhost:8123/bar", "localhost:8123/bar?x1") 506 for url in expected: 507 with self.subTest(url=url): 508 result_default = f"https://{url}" 509 self.assertEqual(ObjectParser.parse_fuzzy_url_str(url), result_default) 510 parsed = ObjectParser.parse_fuzzy_url(url) 511 self.assertEqual(urlparse.urlunparse(parsed), result_default) 512 result_custom = f"ftp://{url}" 513 self.assertEqual( 514 ObjectParser.parse_fuzzy_url_str(url, default_scheme="ftp"), 515 result_custom) 516 parsed = ObjectParser.parse_fuzzy_url(url, default_scheme="ftp") 517 self.assertEqual(urlparse.urlunparse(parsed), result_custom) 518 519 def test_parse_url(self): 520 expected = ( 521 ("file:///foo/bar", "file:///foo/bar"), 522 ("about:blank", "about:blank"), 523 ("http://test.com/bar", "http://test.com/bar"), 524 ("https://test.com/bar", "https://test.com/bar"), 525 ("http://test.com", "http://test.com"), 526 ("https://test.com/", "https://test.com/"), 527 ("http://test.com/bar", "http://test.com/bar"), 528 ("https://test.com/bar?x=1", "https://test.com/bar?x=1"), 529 ("http://test.com:1234", "http://test.com:1234"), 530 ("https://test.com:1234/", "https://test.com:1234/"), 531 ("http://test.com:1234/bar", "http://test.com:1234/bar"), 532 ("https://test.com:1234/bar?x=1", "https://test.com:1234/bar?x=1"), 533 ("http://localhost:8123", "http://localhost:8123"), 534 ("https://localhost:8123/", "https://localhost:8123/"), 535 ("http://localhost:8123/bar", "http://localhost:8123/bar"), 536 ("https://localhost:8123/bar?x=1", "https://localhost:8123/bar?x=1"), 537 ) 538 for url, result in expected: 539 with self.subTest(url=url): 540 self.assertEqual(ObjectParser.url_str(url), result) 541 self.assertEqual(ObjectParser.parse_fuzzy_url_str(url), result) 542 parsed = ObjectParser.url(url) 543 self.assertEqual(urlparse.urlunparse(parsed), result) 544 parsed_fuzzy = ObjectParser.parse_fuzzy_url(url) 545 self.assertEqual(urlparse.urlunparse(parsed_fuzzy), result) 546 547 def test_parse_url_invalid(self): 548 for invalid in (None, "", {}, "http:// foo .com/bar", "htt p://foo.com", 549 "http://foo.com:-123/bar"): 550 with self.subTest(invalid=invalid): 551 with self.assertRaises(argparse.ArgumentTypeError): 552 _ = ObjectParser.url(invalid) 553 with self.assertRaises(argparse.ArgumentTypeError): 554 _ = ObjectParser.url_str(invalid) 555 with self.assertRaises(argparse.ArgumentTypeError): 556 _ = ObjectParser.httpx_url_str(invalid) 557 with self.assertRaises(argparse.ArgumentTypeError): 558 _ = ObjectParser.parse_fuzzy_url_str(invalid) 559 with self.assertRaises(argparse.ArgumentTypeError): 560 _ = ObjectParser.parse_fuzzy_url(invalid) 561 562 def test_parse_httpx_url_str_invalid(self): 563 for invalid in ("ftp://foo.com:123/bar", "ssh://test.com"): 564 with self.subTest(invalid=invalid): 565 with self.assertRaises(argparse.ArgumentTypeError): 566 _ = ObjectParser.httpx_url_str(invalid) 567 568 def test_parse_url_scheme(self): 569 url = "ftp://foo.com" 570 parsed = ObjectParser.url(url) 571 self.assertEqual(urlparse.urlunparse(parsed), url) 572 with self.assertRaises(argparse.ArgumentTypeError): 573 _ = ObjectParser.url(url, schemes=("https",)) 574 parsed = ObjectParser.url( 575 url, schemes=( 576 "https", 577 "ftp", 578 )) 579 self.assertEqual(urlparse.urlunparse(parsed), url) 580 581 def test_parse_regexp(self): 582 with self.assertRaises(argparse.ArgumentTypeError): 583 ObjectParser.regexp("\\") 584 pattern = ObjectParser.regexp("^abc$") 585 self.assertEqual(pattern.pattern, "^abc$") 586 587 588if __name__ == "__main__": 589 test_helper.run_pytest(__file__) 590