1# This file is part of Buildbot. Buildbot is free software: you can 2# redistribute it and/or modify it under the terms of the GNU General Public 3# License as published by the Free Software Foundation, version 2. 4# 5# This program is distributed in the hope that it will be useful, but WITHOUT 6# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 7# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 8# details. 9# 10# You should have received a copy of the GNU General Public License along with 11# this program; if not, write to the Free Software Foundation, Inc., 51 12# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 13# 14# Copyright Buildbot Team Members 15 16import datetime 17import locale 18import os 19 20import mock 21 22from twisted.internet import reactor 23from twisted.internet import task 24from twisted.trial import unittest 25 26from buildbot import util 27 28 29class formatInterval(unittest.TestCase): 30 31 def test_zero(self): 32 self.assertEqual(util.formatInterval(0), "0 secs") 33 34 def test_seconds_singular(self): 35 self.assertEqual(util.formatInterval(1), "1 secs") 36 37 def test_seconds(self): 38 self.assertEqual(util.formatInterval(7), "7 secs") 39 40 def test_minutes_one(self): 41 self.assertEqual(util.formatInterval(60), "60 secs") 42 43 def test_minutes_over_one(self): 44 self.assertEqual(util.formatInterval(61), "1 mins, 1 secs") 45 46 def test_minutes(self): 47 self.assertEqual(util.formatInterval(300), "5 mins, 0 secs") 48 49 def test_hours_one(self): 50 self.assertEqual(util.formatInterval(3600), "60 mins, 0 secs") 51 52 def test_hours_over_one_sec(self): 53 self.assertEqual(util.formatInterval(3601), "1 hrs, 1 secs") 54 55 def test_hours_over_one_min(self): 56 self.assertEqual(util.formatInterval(3660), "1 hrs, 60 secs") 57 58 def test_hours(self): 59 self.assertEqual(util.formatInterval(7200), "2 hrs, 0 secs") 60 61 def test_mixed(self): 62 self.assertEqual(util.formatInterval(7392), "2 hrs, 3 mins, 12 secs") 63 64 65class TestHumanReadableDelta(unittest.TestCase): 66 67 def test_timeDeltaToHumanReadable(self): 68 """ 69 It will return a human readable time difference. 70 """ 71 try: 72 datetime.datetime.fromtimestamp(1) 73 except OSError as e: 74 raise unittest.SkipTest( 75 "Python 3.6 bug on Windows: " 76 "https://bugs.python.org/issue29097") from e 77 result = util.human_readable_delta(1, 1) 78 self.assertEqual('super fast', result) 79 80 result = util.human_readable_delta(1, 2) 81 self.assertEqual('1 seconds', result) 82 83 result = util.human_readable_delta(1, 61) 84 self.assertEqual('1 minutes', result) 85 86 result = util.human_readable_delta(1, 62) 87 self.assertEqual('1 minutes, 1 seconds', result) 88 89 result = util.human_readable_delta(1, 60 * 60 + 1) 90 self.assertEqual('1 hours', result) 91 92 result = util.human_readable_delta(1, 60 * 60 + 61) 93 self.assertEqual('1 hours, 1 minutes', result) 94 95 result = util.human_readable_delta(1, 60 * 60 + 62) 96 self.assertEqual('1 hours, 1 minutes, 1 seconds', result) 97 98 result = util.human_readable_delta(1, 24 * 60 * 60 + 1) 99 self.assertEqual('1 days', result) 100 101 result = util.human_readable_delta(1, 24 * 60 * 60 + 2) 102 self.assertEqual('1 days, 1 seconds', result) 103 104 105class TestFuzzyInterval(unittest.TestCase): 106 107 def test_moment(self): 108 self.assertEqual(util.fuzzyInterval(1), "a moment") 109 110 def test_seconds(self): 111 self.assertEqual(util.fuzzyInterval(17), "17 seconds") 112 113 def test_seconds_rounded(self): 114 self.assertEqual(util.fuzzyInterval(48), "50 seconds") 115 116 def test_minute(self): 117 self.assertEqual(util.fuzzyInterval(58), "a minute") 118 119 def test_minutes(self): 120 self.assertEqual(util.fuzzyInterval(3 * 60 + 24), "3 minutes") 121 122 def test_minutes_rounded(self): 123 self.assertEqual(util.fuzzyInterval(32 * 60 + 24), "30 minutes") 124 125 def test_hour(self): 126 self.assertEqual(util.fuzzyInterval(3600 + 1200), "an hour") 127 128 def test_hours(self): 129 self.assertEqual(util.fuzzyInterval(9 * 3600 - 720), "9 hours") 130 131 def test_day(self): 132 self.assertEqual(util.fuzzyInterval(32 * 3600 + 124), "a day") 133 134 def test_days(self): 135 self.assertEqual(util.fuzzyInterval((19 + 24) * 3600 + 124), "2 days") 136 137 def test_month(self): 138 self.assertEqual(util.fuzzyInterval(36 * 24 * 3600 + 124), "a month") 139 140 def test_months(self): 141 self.assertEqual(util.fuzzyInterval(86 * 24 * 3600 + 124), "3 months") 142 143 def test_year(self): 144 self.assertEqual(util.fuzzyInterval(370 * 24 * 3600), "a year") 145 146 def test_years(self): 147 self.assertEqual(util.fuzzyInterval((2 * 365 + 96) * 24 * 3600), "2 years") 148 149 150class safeTranslate(unittest.TestCase): 151 152 def test_str_good(self): 153 self.assertEqual(util.safeTranslate(str("full")), b"full") 154 155 def test_str_bad(self): 156 self.assertEqual(util.safeTranslate(str("speed=slow;quality=high")), 157 b"speed_slow_quality_high") 158 159 def test_str_pathological(self): 160 # if you needed proof this wasn't for use with sensitive data 161 self.assertEqual(util.safeTranslate(str("p\ath\x01ogy")), 162 b"p\ath\x01ogy") # bad chars still here! 163 164 def test_unicode_good(self): 165 self.assertEqual(util.safeTranslate("full"), b"full") 166 167 def test_unicode_bad(self): 168 self.assertEqual(util.safeTranslate(str("speed=slow;quality=high")), 169 b"speed_slow_quality_high") 170 171 def test_unicode_pathological(self): 172 self.assertEqual(util.safeTranslate("\u0109"), 173 b"\xc4\x89") # yuck! 174 175 176class naturalSort(unittest.TestCase): 177 178 def test_alpha(self): 179 self.assertEqual( 180 util.naturalSort(['x', 'aa', 'ab']), 181 ['aa', 'ab', 'x']) 182 183 def test_numeric(self): 184 self.assertEqual( 185 util.naturalSort(['1', '10', '11', '2', '20']), 186 ['1', '2', '10', '11', '20']) 187 188 def test_alphanum(self): 189 l1 = 'aa10ab aa1ab aa10aa f a aa3 aa30 aa3a aa30a'.split() 190 l2 = 'a aa1ab aa3 aa3a aa10aa aa10ab aa30 aa30a f'.split() 191 self.assertEqual(util.naturalSort(l1), l2) 192 193 194class none_or_str(unittest.TestCase): 195 196 def test_none(self): 197 self.assertEqual(util.none_or_str(None), None) 198 199 def test_str(self): 200 self.assertEqual(util.none_or_str("hi"), "hi") 201 202 def test_int(self): 203 self.assertEqual(util.none_or_str(199), "199") 204 205 206class TimeFunctions(unittest.TestCase): 207 208 def test_UTC(self): 209 self.assertEqual(util.UTC.utcoffset(datetime.datetime.now()), 210 datetime.timedelta(0)) 211 self.assertEqual(util.UTC.dst(datetime.datetime.now()), 212 datetime.timedelta(0)) 213 self.assertEqual(util.UTC.tzname(datetime.datetime.utcnow()), "UTC") 214 215 def test_epoch2datetime(self): 216 self.assertEqual(util.epoch2datetime(0), 217 datetime.datetime(1970, 1, 1, 0, 0, 0, tzinfo=util.UTC)) 218 self.assertEqual(util.epoch2datetime(1300000000), 219 datetime.datetime(2011, 3, 13, 7, 6, 40, tzinfo=util.UTC)) 220 221 def test_datetime2epoch(self): 222 dt = datetime.datetime(1970, 1, 1, 0, 0, 0, tzinfo=util.UTC) 223 self.assertEqual(util.datetime2epoch(dt), 0) 224 dt = datetime.datetime(2011, 3, 13, 7, 6, 40, tzinfo=util.UTC) 225 self.assertEqual(util.datetime2epoch(dt), 1300000000) 226 227 228class DiffSets(unittest.TestCase): 229 230 def test_empty(self): 231 removed, added = util.diffSets(set([]), set([])) 232 self.assertEqual((removed, added), (set([]), set([]))) 233 234 def test_no_lists(self): 235 removed, added = util.diffSets([1, 2], [2, 3]) 236 self.assertEqual((removed, added), (set([1]), set([3]))) 237 238 def test_no_overlap(self): 239 removed, added = util.diffSets(set([1, 2]), set([3, 4])) 240 self.assertEqual((removed, added), (set([1, 2]), set([3, 4]))) 241 242 def test_no_change(self): 243 removed, added = util.diffSets(set([1, 2]), set([1, 2])) 244 self.assertEqual((removed, added), (set([]), set([]))) 245 246 def test_added(self): 247 removed, added = util.diffSets(set([1, 2]), set([1, 2, 3])) 248 self.assertEqual((removed, added), (set([]), set([3]))) 249 250 def test_removed(self): 251 removed, added = util.diffSets(set([1, 2]), set([1])) 252 self.assertEqual((removed, added), (set([2]), set([]))) 253 254 255class MakeList(unittest.TestCase): 256 257 def test_empty_string(self): 258 self.assertEqual(util.makeList(''), ['']) 259 260 def test_None(self): 261 self.assertEqual(util.makeList(None), []) 262 263 def test_string(self): 264 self.assertEqual(util.makeList('hello'), ['hello']) 265 266 def test_unicode(self): 267 self.assertEqual(util.makeList('\N{SNOWMAN}'), ['\N{SNOWMAN}']) 268 269 def test_list(self): 270 self.assertEqual(util.makeList(['a', 'b']), ['a', 'b']) 271 272 def test_tuple(self): 273 self.assertEqual(util.makeList(('a', 'b')), ['a', 'b']) 274 275 def test_copy(self): 276 input = ['a', 'b'] 277 output = util.makeList(input) 278 input.append('c') 279 self.assertEqual(output, ['a', 'b']) 280 281 282class Flatten(unittest.TestCase): 283 284 def test_simple(self): 285 self.assertEqual(util.flatten([1, 2, 3]), [1, 2, 3]) 286 287 def test_deep(self): 288 self.assertEqual(util.flatten([[1, 2], 3, [[4]]]), 289 [1, 2, 3, 4]) 290 291 # def test_deeply_nested(self): 292 # self.assertEqual(util.flatten([5, [6, (7, 8)]]), 293 # [5, 6, 7, 8]) 294 295 # def test_tuples(self): 296 # self.assertEqual(util.flatten([(1, 2), 3]), [1, 2, 3]) 297 298 def test_dict(self): 299 d = {'a': [5, 6, 7], 'b': [7, 8, 9]} 300 self.assertEqual(util.flatten(d), d) 301 302 def test_string(self): 303 self.assertEqual(util.flatten("abc"), "abc") 304 305 306class Ascii2Unicode(unittest.TestCase): 307 308 def test_unicode(self): 309 rv = util.bytes2unicode('\N{SNOWMAN}', encoding='ascii') 310 self.assertEqual((rv, type(rv)), ('\N{SNOWMAN}', str)) 311 312 def test_ascii(self): 313 rv = util.bytes2unicode('abcd', encoding='ascii') 314 self.assertEqual((rv, type(rv)), ('abcd', str)) 315 316 def test_nonascii(self): 317 with self.assertRaises(UnicodeDecodeError): 318 util.bytes2unicode(b'a\x85', encoding='ascii') 319 320 def test_None(self): 321 self.assertEqual(util.bytes2unicode(None, encoding='ascii'), None) 322 323 def test_bytes2unicode(self): 324 rv1 = util.bytes2unicode(b'abcd') 325 rv2 = util.bytes2unicode('efgh') 326 327 self.assertEqual(type(rv1), str) 328 self.assertEqual(type(rv2), str) 329 330 331class StringToBoolean(unittest.TestCase): 332 333 def test_it(self): 334 stringValues = [ 335 (b'on', True), 336 (b'true', True), 337 (b'yes', True), 338 (b'1', True), 339 (b'off', False), 340 (b'false', False), 341 (b'no', False), 342 (b'0', False), 343 (b'ON', True), 344 (b'TRUE', True), 345 (b'YES', True), 346 (b'OFF', False), 347 (b'FALSE', False), 348 (b'NO', False), 349 ] 350 for s, b in stringValues: 351 self.assertEqual(util.string2boolean(s), b, repr(s)) 352 353 def test_ascii(self): 354 rv = util.bytes2unicode(b'abcd', encoding='ascii') 355 self.assertEqual((rv, type(rv)), ('abcd', str)) 356 357 def test_nonascii(self): 358 with self.assertRaises(UnicodeDecodeError): 359 util.bytes2unicode(b'a\x85', encoding='ascii') 360 361 def test_None(self): 362 self.assertEqual(util.bytes2unicode(None, encoding='ascii'), None) 363 364 365class AsyncSleep(unittest.TestCase): 366 367 def test_sleep(self): 368 clock = task.Clock() 369 self.patch(reactor, 'callLater', clock.callLater) 370 d = util.asyncSleep(2) 371 self.assertFalse(d.called) 372 clock.advance(1) 373 self.assertFalse(d.called) 374 clock.advance(1) 375 self.assertTrue(d.called) 376 377 378class FunctionalEnvironment(unittest.TestCase): 379 380 def test_working_locale(self): 381 environ = {'LANG': 'en_GB.UTF-8'} 382 self.patch(os, 'environ', environ) 383 config = mock.Mock() 384 util.check_functional_environment(config) 385 self.assertEqual(config.error.called, False) 386 387 def test_broken_locale(self): 388 def err(): 389 raise KeyError 390 self.patch(locale, 'getdefaultlocale', err) 391 config = mock.Mock() 392 util.check_functional_environment(config) 393 config.error.assert_called_with(mock.ANY) 394 395 396class StripUrlPassword(unittest.TestCase): 397 398 def test_simple_url(self): 399 self.assertEqual(util.stripUrlPassword('http://foo.com/bar'), 400 'http://foo.com/bar') 401 402 def test_username(self): 403 self.assertEqual(util.stripUrlPassword('http://d@foo.com/bar'), 404 'http://d@foo.com/bar') 405 406 def test_username_with_at(self): 407 self.assertEqual(util.stripUrlPassword('http://d@bb.net@foo.com/bar'), 408 'http://d@bb.net@foo.com/bar') 409 410 def test_username_pass(self): 411 self.assertEqual(util.stripUrlPassword('http://d:secret@foo.com/bar'), 412 'http://d:xxxx@foo.com/bar') 413 414 def test_username_pass_with_at(self): 415 self.assertEqual( 416 util.stripUrlPassword('http://d@bb.net:scrt@foo.com/bar'), 417 'http://d@bb.net:xxxx@foo.com/bar') 418 419 420class JoinList(unittest.TestCase): 421 422 def test_list(self): 423 self.assertEqual(util.join_list(['aa', 'bb']), 'aa bb') 424 425 def test_tuple(self): 426 self.assertEqual(util.join_list(('aa', 'bb')), 'aa bb') 427 428 def test_string(self): 429 self.assertEqual(util.join_list('abc'), 'abc') 430 431 def test_unicode(self): 432 self.assertEqual(util.join_list('abc'), 'abc') 433 434 def test_nonascii(self): 435 with self.assertRaises(UnicodeDecodeError): 436 util.join_list([b'\xff']) 437 438 439class CommandToString(unittest.TestCase): 440 441 def test_short_string(self): 442 self.assertEqual(util.command_to_string("ab cd"), "'ab cd'") 443 444 def test_long_string(self): 445 self.assertEqual(util.command_to_string("ab cd ef"), "'ab cd ...'") 446 447 def test_list(self): 448 self.assertEqual(util.command_to_string(['ab', 'cd', 'ef']), 449 "'ab cd ...'") 450 451 def test_nested_list(self): 452 self.assertEqual(util.command_to_string(['ab', ['cd', ['ef']]]), 453 "'ab cd ...'") 454 455 def test_object(self): 456 # this looks like a renderable 457 self.assertEqual(util.command_to_string(object()), None) 458 459 def test_list_with_objects(self): 460 # the object looks like a renderable, and is skipped 461 self.assertEqual(util.command_to_string(['ab', object(), 'cd']), 462 "'ab cd'") 463 464 def test_invalid_ascii(self): 465 self.assertEqual(util.command_to_string(b'a\xffc'), "'a\ufffdc'") 466 467 468class TestRewrap(unittest.TestCase): 469 470 def test_main(self): 471 tests = [ 472 ("", "", None), 473 ("\n", "\n", None), 474 ("\n ", "\n", None), 475 (" \n", "\n", None), 476 (" \n ", "\n", None), 477 (""" 478 multiline 479 with 480 indent 481 """, 482 "\nmultiline with indent", 483 None), 484 ("""\ 485 multiline 486 with 487 indent 488 489 """, 490 "multiline with indent\n", 491 None), 492 ("""\ 493 multiline 494 with 495 indent 496 497 """, 498 "multiline with indent\n", 499 None), 500 ("""\ 501 multiline 502 with 503 indent 504 505 and 506 formatting 507 """, 508 "multiline with indent\n and\n formatting\n", 509 None), 510 ("""\ 511 multiline 512 with 513 indent 514 and wrapping 515 516 and 517 formatting 518 """, 519 "multiline with\nindent and\nwrapping\n and\n formatting\n", 520 15), 521 ] 522 523 for text, expected, width in tests: 524 self.assertEqual(util.rewrap(text, width=width), expected) 525 526 527class TestMerge(unittest.TestCase): 528 529 def test_merge(self): 530 self.assertEqual( 531 util.dictionary_merge( 532 { 533 'a': {'b': 1} 534 }, 535 { 536 'a': {'c': 2} 537 }), 538 { 539 'a': {'b': 1, 'c': 2} 540 }) 541 542 def test_overwrite(self): 543 self.assertEqual( 544 util.dictionary_merge( 545 { 546 'a': {'b': 1} 547 }, 548 { 549 'a': 1 550 }), 551 { 552 'a': 1 553 }) 554 555 def test_overwrite2(self): 556 self.assertEqual( 557 util.dictionary_merge( 558 { 559 'a': {'b': 1, 'c': 2} 560 }, 561 { 562 'a': {'b': [1, 2, 3]} 563 }), 564 { 565 'a': {'b': [1, 2, 3], 'c': 2} 566 }) 567