From 545848f16474227d3cce139e26dc5b4bb1f577cc Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 16 Jul 2020 14:57:09 -0400 Subject: [PATCH 01/22] Move abbreviate tests into their own module. --- src/allmydata/test/test_util.py | 128 ---------------------------- src/allmydata/test_abbreviate.py | 139 +++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 128 deletions(-) create mode 100644 src/allmydata/test_abbreviate.py diff --git a/src/allmydata/test/test_util.py b/src/allmydata/test/test_util.py index f744feb7e..c6b4dd991 100644 --- a/src/allmydata/test/test_util.py +++ b/src/allmydata/test/test_util.py @@ -687,134 +687,6 @@ class HashUtilTests(unittest.TestCase): base32.a2b("u33m4y7klhz3bypswqkozwetvabelhxt"), # seed ) -class Abbreviate(unittest.TestCase): - def test_abbrev_time_1s(self): - diff = timedelta(seconds=1) - s = abbreviate.abbreviate_time(diff) - self.assertEqual('1 second ago', s) - - def test_abbrev_time_25s(self): - diff = timedelta(seconds=25) - s = abbreviate.abbreviate_time(diff) - self.assertEqual('25 seconds ago', s) - - def test_abbrev_time_future_5_minutes(self): - diff = timedelta(minutes=-5) - s = abbreviate.abbreviate_time(diff) - self.assertEqual('5 minutes in the future', s) - - def test_abbrev_time_hours(self): - diff = timedelta(hours=4) - s = abbreviate.abbreviate_time(diff) - self.assertEqual('4 hours ago', s) - - def test_abbrev_time_day(self): - diff = timedelta(hours=49) # must be more than 2 days - s = abbreviate.abbreviate_time(diff) - self.assertEqual('2 days ago', s) - - def test_abbrev_time_month(self): - diff = timedelta(days=91) - s = abbreviate.abbreviate_time(diff) - self.assertEqual('3 months ago', s) - - def test_abbrev_time_year(self): - diff = timedelta(weeks=(5 * 52) + 1) - s = abbreviate.abbreviate_time(diff) - self.assertEqual('5 years ago', s) - - def test_time(self): - a = abbreviate.abbreviate_time - self.failUnlessEqual(a(None), "unknown") - self.failUnlessEqual(a(0), "0 seconds") - self.failUnlessEqual(a(1), "1 second") - self.failUnlessEqual(a(2), "2 seconds") - self.failUnlessEqual(a(119), "119 seconds") - MIN = 60 - self.failUnlessEqual(a(2*MIN), "2 minutes") - self.failUnlessEqual(a(60*MIN), "60 minutes") - self.failUnlessEqual(a(179*MIN), "179 minutes") - HOUR = 60*MIN - self.failUnlessEqual(a(180*MIN), "3 hours") - self.failUnlessEqual(a(4*HOUR), "4 hours") - DAY = 24*HOUR - MONTH = 30*DAY - self.failUnlessEqual(a(2*DAY), "2 days") - self.failUnlessEqual(a(2*MONTH), "2 months") - YEAR = 365*DAY - self.failUnlessEqual(a(5*YEAR), "5 years") - - def test_space(self): - tests_si = [(None, "unknown"), - (0, "0 B"), - (1, "1 B"), - (999, "999 B"), - (1000, "1000 B"), - (1023, "1023 B"), - (1024, "1.02 kB"), - (20*1000, "20.00 kB"), - (1024*1024, "1.05 MB"), - (1000*1000, "1.00 MB"), - (1000*1000*1000, "1.00 GB"), - (1000*1000*1000*1000, "1.00 TB"), - (1000*1000*1000*1000*1000, "1.00 PB"), - (1000*1000*1000*1000*1000*1000, "1.00 EB"), - (1234567890123456789, "1.23 EB"), - ] - for (x, expected) in tests_si: - got = abbreviate.abbreviate_space(x, SI=True) - self.failUnlessEqual(got, expected) - - tests_base1024 = [(None, "unknown"), - (0, "0 B"), - (1, "1 B"), - (999, "999 B"), - (1000, "1000 B"), - (1023, "1023 B"), - (1024, "1.00 kiB"), - (20*1024, "20.00 kiB"), - (1000*1000, "976.56 kiB"), - (1024*1024, "1.00 MiB"), - (1024*1024*1024, "1.00 GiB"), - (1024*1024*1024*1024, "1.00 TiB"), - (1000*1000*1000*1000*1000, "909.49 TiB"), - (1024*1024*1024*1024*1024, "1.00 PiB"), - (1024*1024*1024*1024*1024*1024, "1.00 EiB"), - (1234567890123456789, "1.07 EiB"), - ] - for (x, expected) in tests_base1024: - got = abbreviate.abbreviate_space(x, SI=False) - self.failUnlessEqual(got, expected) - - self.failUnlessEqual(abbreviate.abbreviate_space_both(1234567), - "(1.23 MB, 1.18 MiB)") - - def test_parse_space(self): - p = abbreviate.parse_abbreviated_size - self.failUnlessEqual(p(""), None) - self.failUnlessEqual(p(None), None) - self.failUnlessEqual(p("123"), 123) - self.failUnlessEqual(p("123B"), 123) - self.failUnlessEqual(p("2K"), 2000) - self.failUnlessEqual(p("2kb"), 2000) - self.failUnlessEqual(p("2KiB"), 2048) - self.failUnlessEqual(p("10MB"), 10*1000*1000) - self.failUnlessEqual(p("10MiB"), 10*1024*1024) - self.failUnlessEqual(p("5G"), 5*1000*1000*1000) - self.failUnlessEqual(p("4GiB"), 4*1024*1024*1024) - self.failUnlessEqual(p("3TB"), 3*1000*1000*1000*1000) - self.failUnlessEqual(p("3TiB"), 3*1024*1024*1024*1024) - self.failUnlessEqual(p("6PB"), 6*1000*1000*1000*1000*1000) - self.failUnlessEqual(p("6PiB"), 6*1024*1024*1024*1024*1024) - self.failUnlessEqual(p("9EB"), 9*1000*1000*1000*1000*1000*1000) - self.failUnlessEqual(p("9EiB"), 9*1024*1024*1024*1024*1024*1024) - - e = self.failUnlessRaises(ValueError, p, "12 cubits") - self.failUnlessIn("12 cubits", str(e)) - e = self.failUnlessRaises(ValueError, p, "1 BB") - self.failUnlessIn("1 BB", str(e)) - e = self.failUnlessRaises(ValueError, p, "fhtagn") - self.failUnlessIn("fhtagn", str(e)) class Limiter(unittest.TestCase): diff --git a/src/allmydata/test_abbreviate.py b/src/allmydata/test_abbreviate.py new file mode 100644 index 000000000..483ccfda0 --- /dev/null +++ b/src/allmydata/test_abbreviate.py @@ -0,0 +1,139 @@ +""" +Tests for allmydata.util.abbreviate. +""" + +from datetime import timedelta + +from twisted.trial import unittest + +from allmydata.util import abbreviate + + +class Abbreviate(unittest.TestCase): + def test_abbrev_time_1s(self): + diff = timedelta(seconds=1) + s = abbreviate.abbreviate_time(diff) + self.assertEqual('1 second ago', s) + + def test_abbrev_time_25s(self): + diff = timedelta(seconds=25) + s = abbreviate.abbreviate_time(diff) + self.assertEqual('25 seconds ago', s) + + def test_abbrev_time_future_5_minutes(self): + diff = timedelta(minutes=-5) + s = abbreviate.abbreviate_time(diff) + self.assertEqual('5 minutes in the future', s) + + def test_abbrev_time_hours(self): + diff = timedelta(hours=4) + s = abbreviate.abbreviate_time(diff) + self.assertEqual('4 hours ago', s) + + def test_abbrev_time_day(self): + diff = timedelta(hours=49) # must be more than 2 days + s = abbreviate.abbreviate_time(diff) + self.assertEqual('2 days ago', s) + + def test_abbrev_time_month(self): + diff = timedelta(days=91) + s = abbreviate.abbreviate_time(diff) + self.assertEqual('3 months ago', s) + + def test_abbrev_time_year(self): + diff = timedelta(weeks=(5 * 52) + 1) + s = abbreviate.abbreviate_time(diff) + self.assertEqual('5 years ago', s) + + def test_time(self): + a = abbreviate.abbreviate_time + self.failUnlessEqual(a(None), "unknown") + self.failUnlessEqual(a(0), "0 seconds") + self.failUnlessEqual(a(1), "1 second") + self.failUnlessEqual(a(2), "2 seconds") + self.failUnlessEqual(a(119), "119 seconds") + MIN = 60 + self.failUnlessEqual(a(2*MIN), "2 minutes") + self.failUnlessEqual(a(60*MIN), "60 minutes") + self.failUnlessEqual(a(179*MIN), "179 minutes") + HOUR = 60*MIN + self.failUnlessEqual(a(180*MIN), "3 hours") + self.failUnlessEqual(a(4*HOUR), "4 hours") + DAY = 24*HOUR + MONTH = 30*DAY + self.failUnlessEqual(a(2*DAY), "2 days") + self.failUnlessEqual(a(2*MONTH), "2 months") + YEAR = 365*DAY + self.failUnlessEqual(a(5*YEAR), "5 years") + + def test_space(self): + tests_si = [(None, "unknown"), + (0, "0 B"), + (1, "1 B"), + (999, "999 B"), + (1000, "1000 B"), + (1023, "1023 B"), + (1024, "1.02 kB"), + (20*1000, "20.00 kB"), + (1024*1024, "1.05 MB"), + (1000*1000, "1.00 MB"), + (1000*1000*1000, "1.00 GB"), + (1000*1000*1000*1000, "1.00 TB"), + (1000*1000*1000*1000*1000, "1.00 PB"), + (1000*1000*1000*1000*1000*1000, "1.00 EB"), + (1234567890123456789, "1.23 EB"), + ] + for (x, expected) in tests_si: + got = abbreviate.abbreviate_space(x, SI=True) + self.failUnlessEqual(got, expected) + + tests_base1024 = [(None, "unknown"), + (0, "0 B"), + (1, "1 B"), + (999, "999 B"), + (1000, "1000 B"), + (1023, "1023 B"), + (1024, "1.00 kiB"), + (20*1024, "20.00 kiB"), + (1000*1000, "976.56 kiB"), + (1024*1024, "1.00 MiB"), + (1024*1024*1024, "1.00 GiB"), + (1024*1024*1024*1024, "1.00 TiB"), + (1000*1000*1000*1000*1000, "909.49 TiB"), + (1024*1024*1024*1024*1024, "1.00 PiB"), + (1024*1024*1024*1024*1024*1024, "1.00 EiB"), + (1234567890123456789, "1.07 EiB"), + ] + for (x, expected) in tests_base1024: + got = abbreviate.abbreviate_space(x, SI=False) + self.failUnlessEqual(got, expected) + + self.failUnlessEqual(abbreviate.abbreviate_space_both(1234567), + "(1.23 MB, 1.18 MiB)") + + def test_parse_space(self): + p = abbreviate.parse_abbreviated_size + self.failUnlessEqual(p(""), None) + self.failUnlessEqual(p(None), None) + self.failUnlessEqual(p("123"), 123) + self.failUnlessEqual(p("123B"), 123) + self.failUnlessEqual(p("2K"), 2000) + self.failUnlessEqual(p("2kb"), 2000) + self.failUnlessEqual(p("2KiB"), 2048) + self.failUnlessEqual(p("10MB"), 10*1000*1000) + self.failUnlessEqual(p("10MiB"), 10*1024*1024) + self.failUnlessEqual(p("5G"), 5*1000*1000*1000) + self.failUnlessEqual(p("4GiB"), 4*1024*1024*1024) + self.failUnlessEqual(p("3TB"), 3*1000*1000*1000*1000) + self.failUnlessEqual(p("3TiB"), 3*1024*1024*1024*1024) + self.failUnlessEqual(p("6PB"), 6*1000*1000*1000*1000*1000) + self.failUnlessEqual(p("6PiB"), 6*1024*1024*1024*1024*1024) + self.failUnlessEqual(p("9EB"), 9*1000*1000*1000*1000*1000*1000) + self.failUnlessEqual(p("9EiB"), 9*1024*1024*1024*1024*1024*1024) + + e = self.failUnlessRaises(ValueError, p, "12 cubits") + self.failUnlessIn("12 cubits", str(e)) + e = self.failUnlessRaises(ValueError, p, "1 BB") + self.failUnlessIn("1 BB", str(e)) + e = self.failUnlessRaises(ValueError, p, "fhtagn") + self.failUnlessIn("fhtagn", str(e)) From 72272cbf0bd0f8d644f1d8debb8fce22df6f6295 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 16 Jul 2020 15:15:26 -0400 Subject: [PATCH 02/22] Move out tests for time_format. --- src/allmydata/test/test_time_format.py | 146 +++++++++++++++++++++++++ src/allmydata/test/test_util.py | 135 ----------------------- 2 files changed, 146 insertions(+), 135 deletions(-) create mode 100644 src/allmydata/test/test_time_format.py diff --git a/src/allmydata/test/test_time_format.py b/src/allmydata/test/test_time_format.py new file mode 100644 index 000000000..ae655ff3e --- /dev/null +++ b/src/allmydata/test/test_time_format.py @@ -0,0 +1,146 @@ +""" +Tests for allmydata.util.time_format. +""" + +import time + +from twisted.trial import unittest + +from allmydata.test.common_util import TimezoneMixin +from allmydata.util import time_format + + +class TimeFormat(unittest.TestCase, TimezoneMixin): + def test_epoch(self): + return self._help_test_epoch() + + def test_epoch_in_London(self): + # Europe/London is a particularly troublesome timezone. Nowadays, its + # offset from GMT is 0. But in 1970, its offset from GMT was 1. + # (Apparently in 1970 Britain had redefined standard time to be GMT+1 + # and stayed in standard time all year round, whereas today + # Europe/London standard time is GMT and Europe/London Daylight + # Savings Time is GMT+1.) The current implementation of + # time_format.iso_utc_time_to_localseconds() breaks if the timezone is + # Europe/London. (As soon as this unit test is done then I'll change + # that implementation to something that works even in this case...) + + if not self.have_working_tzset(): + raise unittest.SkipTest("This test can't be run on a platform without time.tzset().") + + self.setTimezone("Europe/London") + return self._help_test_epoch() + + def _help_test_epoch(self): + origtzname = time.tzname + s = time_format.iso_utc_time_to_seconds("1970-01-01T00:00:01") + self.failUnlessEqual(s, 1.0) + s = time_format.iso_utc_time_to_seconds("1970-01-01_00:00:01") + self.failUnlessEqual(s, 1.0) + s = time_format.iso_utc_time_to_seconds("1970-01-01 00:00:01") + self.failUnlessEqual(s, 1.0) + + self.failUnlessEqual(time_format.iso_utc(1.0), "1970-01-01_00:00:01") + self.failUnlessEqual(time_format.iso_utc(1.0, sep=" "), + "1970-01-01 00:00:01") + + now = time.time() + isostr = time_format.iso_utc(now) + timestamp = time_format.iso_utc_time_to_seconds(isostr) + self.failUnlessEqual(int(timestamp), int(now)) + + def my_time(): + return 1.0 + self.failUnlessEqual(time_format.iso_utc(t=my_time), + "1970-01-01_00:00:01") + e = self.failUnlessRaises(ValueError, + time_format.iso_utc_time_to_seconds, + "invalid timestring") + self.failUnless("not a complete ISO8601 timestamp" in str(e)) + s = time_format.iso_utc_time_to_seconds("1970-01-01_00:00:01.500") + self.failUnlessEqual(s, 1.5) + + # Look for daylight-savings-related errors. + thatmomentinmarch = time_format.iso_utc_time_to_seconds("2009-03-20 21:49:02.226536") + self.failUnlessEqual(thatmomentinmarch, 1237585742.226536) + self.failUnlessEqual(origtzname, time.tzname) + + def test_iso_utc(self): + when = 1266760143.7841301 + out = time_format.iso_utc_date(when) + self.failUnlessEqual(out, "2010-02-21") + out = time_format.iso_utc_date(t=lambda: when) + self.failUnlessEqual(out, "2010-02-21") + out = time_format.iso_utc(when) + self.failUnlessEqual(out, "2010-02-21_13:49:03.784130") + out = time_format.iso_utc(when, sep="-") + self.failUnlessEqual(out, "2010-02-21-13:49:03.784130") + + def test_parse_duration(self): + p = time_format.parse_duration + DAY = 24*60*60 + self.failUnlessEqual(p("1 day"), DAY) + self.failUnlessEqual(p("2 days"), 2*DAY) + self.failUnlessEqual(p("3 months"), 3*31*DAY) + self.failUnlessEqual(p("4 mo"), 4*31*DAY) + self.failUnlessEqual(p("5 years"), 5*365*DAY) + e = self.failUnlessRaises(ValueError, p, "123") + self.failUnlessIn("no unit (like day, month, or year) in '123'", + str(e)) + + def test_parse_date(self): + self.failUnlessEqual(time_format.parse_date("2010-02-21"), 1266710400) + + def test_format_time(self): + self.failUnlessEqual(time_format.format_time(time.gmtime(0)), '1970-01-01 00:00:00') + self.failUnlessEqual(time_format.format_time(time.gmtime(60)), '1970-01-01 00:01:00') + self.failUnlessEqual(time_format.format_time(time.gmtime(60*60)), '1970-01-01 01:00:00') + seconds_per_day = 60*60*24 + leap_years_1970_to_2014_inclusive = ((2012 - 1968) // 4) + self.failUnlessEqual(time_format.format_time(time.gmtime(seconds_per_day*((2015 - 1970)*365+leap_years_1970_to_2014_inclusive))), '2015-01-01 00:00:00') + + def test_format_time_y2038(self): + seconds_per_day = 60*60*24 + leap_years_1970_to_2047_inclusive = ((2044 - 1968) // 4) + t = (seconds_per_day* + ((2048 - 1970)*365 + leap_years_1970_to_2047_inclusive)) + try: + gm_t = time.gmtime(t) + except ValueError: + raise unittest.SkipTest("Note: this system cannot handle dates after 2037.") + self.failUnlessEqual(time_format.format_time(gm_t), + '2048-01-01 00:00:00') + + def test_format_delta(self): + time_1 = 1389812723 + time_5s_delta = 1389812728 + time_28m7s_delta = 1389814410 + time_1h_delta = 1389816323 + time_1d21h46m49s_delta = 1389977532 + + self.failUnlessEqual( + time_format.format_delta(time_1, time_1), '0s') + + self.failUnlessEqual( + time_format.format_delta(time_1, time_5s_delta), '5s') + self.failUnlessEqual( + time_format.format_delta(time_1, time_28m7s_delta), '28m 7s') + self.failUnlessEqual( + time_format.format_delta(time_1, time_1h_delta), '1h 0m 0s') + self.failUnlessEqual( + time_format.format_delta(time_1, time_1d21h46m49s_delta), '1d 21h 46m 49s') + + self.failUnlessEqual( + time_format.format_delta(time_1d21h46m49s_delta, time_1), '-') + + # time_1 with a decimal fraction will make the delta 1s less + time_1decimal = 1389812723.383963 + + self.failUnlessEqual( + time_format.format_delta(time_1decimal, time_5s_delta), '4s') + self.failUnlessEqual( + time_format.format_delta(time_1decimal, time_28m7s_delta), '28m 6s') + self.failUnlessEqual( + time_format.format_delta(time_1decimal, time_1h_delta), '59m 59s') + self.failUnlessEqual( + time_format.format_delta(time_1decimal, time_1d21h46m49s_delta), '1d 21h 46m 48s') diff --git a/src/allmydata/test/test_util.py b/src/allmydata/test/test_util.py index c6b4dd991..40a05e9c5 100644 --- a/src/allmydata/test/test_util.py +++ b/src/allmydata/test/test_util.py @@ -762,141 +762,6 @@ class Limiter(unittest.TestCase): d.addCallback(_all_done) return d -class TimeFormat(unittest.TestCase, TimezoneMixin): - def test_epoch(self): - return self._help_test_epoch() - - def test_epoch_in_London(self): - # Europe/London is a particularly troublesome timezone. Nowadays, its - # offset from GMT is 0. But in 1970, its offset from GMT was 1. - # (Apparently in 1970 Britain had redefined standard time to be GMT+1 - # and stayed in standard time all year round, whereas today - # Europe/London standard time is GMT and Europe/London Daylight - # Savings Time is GMT+1.) The current implementation of - # time_format.iso_utc_time_to_localseconds() breaks if the timezone is - # Europe/London. (As soon as this unit test is done then I'll change - # that implementation to something that works even in this case...) - - if not self.have_working_tzset(): - raise unittest.SkipTest("This test can't be run on a platform without time.tzset().") - - self.setTimezone("Europe/London") - return self._help_test_epoch() - - def _help_test_epoch(self): - origtzname = time.tzname - s = time_format.iso_utc_time_to_seconds("1970-01-01T00:00:01") - self.failUnlessEqual(s, 1.0) - s = time_format.iso_utc_time_to_seconds("1970-01-01_00:00:01") - self.failUnlessEqual(s, 1.0) - s = time_format.iso_utc_time_to_seconds("1970-01-01 00:00:01") - self.failUnlessEqual(s, 1.0) - - self.failUnlessEqual(time_format.iso_utc(1.0), "1970-01-01_00:00:01") - self.failUnlessEqual(time_format.iso_utc(1.0, sep=" "), - "1970-01-01 00:00:01") - - now = time.time() - isostr = time_format.iso_utc(now) - timestamp = time_format.iso_utc_time_to_seconds(isostr) - self.failUnlessEqual(int(timestamp), int(now)) - - def my_time(): - return 1.0 - self.failUnlessEqual(time_format.iso_utc(t=my_time), - "1970-01-01_00:00:01") - e = self.failUnlessRaises(ValueError, - time_format.iso_utc_time_to_seconds, - "invalid timestring") - self.failUnless("not a complete ISO8601 timestamp" in str(e)) - s = time_format.iso_utc_time_to_seconds("1970-01-01_00:00:01.500") - self.failUnlessEqual(s, 1.5) - - # Look for daylight-savings-related errors. - thatmomentinmarch = time_format.iso_utc_time_to_seconds("2009-03-20 21:49:02.226536") - self.failUnlessEqual(thatmomentinmarch, 1237585742.226536) - self.failUnlessEqual(origtzname, time.tzname) - - def test_iso_utc(self): - when = 1266760143.7841301 - out = time_format.iso_utc_date(when) - self.failUnlessEqual(out, "2010-02-21") - out = time_format.iso_utc_date(t=lambda: when) - self.failUnlessEqual(out, "2010-02-21") - out = time_format.iso_utc(when) - self.failUnlessEqual(out, "2010-02-21_13:49:03.784130") - out = time_format.iso_utc(when, sep="-") - self.failUnlessEqual(out, "2010-02-21-13:49:03.784130") - - def test_parse_duration(self): - p = time_format.parse_duration - DAY = 24*60*60 - self.failUnlessEqual(p("1 day"), DAY) - self.failUnlessEqual(p("2 days"), 2*DAY) - self.failUnlessEqual(p("3 months"), 3*31*DAY) - self.failUnlessEqual(p("4 mo"), 4*31*DAY) - self.failUnlessEqual(p("5 years"), 5*365*DAY) - e = self.failUnlessRaises(ValueError, p, "123") - self.failUnlessIn("no unit (like day, month, or year) in '123'", - str(e)) - - def test_parse_date(self): - self.failUnlessEqual(time_format.parse_date("2010-02-21"), 1266710400) - - def test_format_time(self): - self.failUnlessEqual(time_format.format_time(time.gmtime(0)), '1970-01-01 00:00:00') - self.failUnlessEqual(time_format.format_time(time.gmtime(60)), '1970-01-01 00:01:00') - self.failUnlessEqual(time_format.format_time(time.gmtime(60*60)), '1970-01-01 01:00:00') - seconds_per_day = 60*60*24 - leap_years_1970_to_2014_inclusive = ((2012 - 1968) // 4) - self.failUnlessEqual(time_format.format_time(time.gmtime(seconds_per_day*((2015 - 1970)*365+leap_years_1970_to_2014_inclusive))), '2015-01-01 00:00:00') - - def test_format_time_y2038(self): - seconds_per_day = 60*60*24 - leap_years_1970_to_2047_inclusive = ((2044 - 1968) // 4) - t = (seconds_per_day* - ((2048 - 1970)*365 + leap_years_1970_to_2047_inclusive)) - try: - gm_t = time.gmtime(t) - except ValueError: - raise unittest.SkipTest("Note: this system cannot handle dates after 2037.") - self.failUnlessEqual(time_format.format_time(gm_t), - '2048-01-01 00:00:00') - - def test_format_delta(self): - time_1 = 1389812723 - time_5s_delta = 1389812728 - time_28m7s_delta = 1389814410 - time_1h_delta = 1389816323 - time_1d21h46m49s_delta = 1389977532 - - self.failUnlessEqual( - time_format.format_delta(time_1, time_1), '0s') - - self.failUnlessEqual( - time_format.format_delta(time_1, time_5s_delta), '5s') - self.failUnlessEqual( - time_format.format_delta(time_1, time_28m7s_delta), '28m 7s') - self.failUnlessEqual( - time_format.format_delta(time_1, time_1h_delta), '1h 0m 0s') - self.failUnlessEqual( - time_format.format_delta(time_1, time_1d21h46m49s_delta), '1d 21h 46m 49s') - - self.failUnlessEqual( - time_format.format_delta(time_1d21h46m49s_delta, time_1), '-') - - # time_1 with a decimal fraction will make the delta 1s less - time_1decimal = 1389812723.383963 - - self.failUnlessEqual( - time_format.format_delta(time_1decimal, time_5s_delta), '4s') - self.failUnlessEqual( - time_format.format_delta(time_1decimal, time_28m7s_delta), '28m 6s') - self.failUnlessEqual( - time_format.format_delta(time_1decimal, time_1h_delta), '59m 59s') - self.failUnlessEqual( - time_format.format_delta(time_1decimal, time_1d21h46m49s_delta), '1d 21h 46m 48s') - ctr = [0] class EqButNotIs(object): From e90d1f38d22bb3e8d5550c4d03d083cc61f91d9b Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 16 Jul 2020 15:44:41 -0400 Subject: [PATCH 03/22] Make TimezoneMixin importable on Python 3. --- src/allmydata/test/common_util.py | 25 ------------------------- src/allmydata/test/test_time_format.py | 2 +- src/allmydata/test/test_util.py | 2 +- src/allmydata/test/web/test_web.py | 3 ++- src/allmydata/util/_python3.py | 1 + 5 files changed, 5 insertions(+), 28 deletions(-) diff --git a/src/allmydata/test/common_util.py b/src/allmydata/test/common_util.py index bf44785a5..2e8bbd006 100644 --- a/src/allmydata/test/common_util.py +++ b/src/allmydata/test/common_util.py @@ -185,31 +185,6 @@ class TestMixin(SignalMixin): self.fail("Reactor was still active when it was required to be quiescent.") -class TimezoneMixin(object): - - def setTimezone(self, timezone): - def tzset_if_possible(): - # Windows doesn't have time.tzset(). - if hasattr(time, 'tzset'): - time.tzset() - - unset = object() - originalTimezone = os.environ.get('TZ', unset) - def restoreTimezone(): - if originalTimezone is unset: - del os.environ['TZ'] - else: - os.environ['TZ'] = originalTimezone - tzset_if_possible() - - os.environ['TZ'] = timezone - self.addCleanup(restoreTimezone) - tzset_if_possible() - - def have_working_tzset(self): - return hasattr(time, 'tzset') - - try: import win32file import win32con diff --git a/src/allmydata/test/test_time_format.py b/src/allmydata/test/test_time_format.py index ae655ff3e..7edf9f12d 100644 --- a/src/allmydata/test/test_time_format.py +++ b/src/allmydata/test/test_time_format.py @@ -6,7 +6,7 @@ import time from twisted.trial import unittest -from allmydata.test.common_util import TimezoneMixin +from allmydata.test.common_py3 import TimezoneMixin from allmydata.util import time_format diff --git a/src/allmydata/test/test_util.py b/src/allmydata/test/test_util.py index 40a05e9c5..c290903a7 100644 --- a/src/allmydata/test/test_util.py +++ b/src/allmydata/test/test_util.py @@ -21,7 +21,7 @@ from allmydata.util import statistics, dictutil, pipeline, yamlutil from allmydata.util import log as tahoe_log from allmydata.util.spans import Spans, overlap, DataSpans from allmydata.util.fileutil import EncryptedTemporaryFile -from allmydata.test.common_util import ReallyEqualMixin, TimezoneMixin +from allmydata.test.common_util import ReallyEqualMixin if six.PY3: long = int diff --git a/src/allmydata/test/web/test_web.py b/src/allmydata/test/web/test_web.py index b3c8c7f46..5678a98f8 100644 --- a/src/allmydata/test/web/test_web.py +++ b/src/allmydata/test/web/test_web.py @@ -60,6 +60,7 @@ from .common import ( from allmydata.interfaces import IMutableFileNode, SDMF_VERSION, MDMF_VERSION from allmydata.mutable import servermap, publish, retrieve from .. import common_util as testutil +from ..common_py3 import TimezoneMixin from ..common_web import ( do_http, Error, @@ -311,7 +312,7 @@ class FakeClient(_Client): MUTABLE_SIZELIMIT = FakeMutableFileNode.MUTABLE_SIZELIMIT -class WebMixin(testutil.TimezoneMixin): +class WebMixin(TimezoneMixin): def setUp(self): self.setTimezone('UTC-13:00') self.s = FakeClient() diff --git a/src/allmydata/util/_python3.py b/src/allmydata/util/_python3.py index aa4e5f386..19bd35890 100644 --- a/src/allmydata/util/_python3.py +++ b/src/allmydata/util/_python3.py @@ -22,6 +22,7 @@ PORTED_MODULES = [ "allmydata.util.namespace", "allmydata.util.pollmixin", "allmydata.util._python3", + "allmydata.test.common_py3", ] PORTED_TEST_MODULES = [ From 4c047b90e5fa6e0e2b14384b2c0016cfa4463716 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 16 Jul 2020 15:46:23 -0400 Subject: [PATCH 04/22] Manual steps of port to Python 3. --- src/allmydata/util/time_format.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/allmydata/util/time_format.py b/src/allmydata/util/time_format.py index eb3434c47..27b2e0976 100644 --- a/src/allmydata/util/time_format.py +++ b/src/allmydata/util/time_format.py @@ -1,5 +1,9 @@ -# ISO-8601: -# http://www.cl.cam.ac.uk/~mgk25/iso-time.html +""" +Time formatting utilities. + +ISO-8601: +http://www.cl.cam.ac.uk/~mgk25/iso-time.html +""" import calendar, datetime, re, time @@ -74,11 +78,11 @@ def format_delta(time_1, time_2): delta = int(time_2 - time_1) seconds = delta % 60 delta -= seconds - minutes = (delta / 60) % 60 + minutes = (delta // 60) % 60 delta -= minutes * 60 - hours = delta / (60*60) % 24 + hours = delta // (60*60) % 24 delta -= hours * 24 - days = delta / (24*60*60) + days = delta // (24*60*60) if not days: if not hours: if not minutes: From a4620bf17675ca9bc902e5f46856ebfcabcfaa8b Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 16 Jul 2020 15:58:39 -0400 Subject: [PATCH 05/22] Automated port of the test module. --- src/allmydata/test/test_time_format.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/allmydata/test/test_time_format.py b/src/allmydata/test/test_time_format.py index 7edf9f12d..54593e5b9 100644 --- a/src/allmydata/test/test_time_format.py +++ b/src/allmydata/test/test_time_format.py @@ -1,6 +1,14 @@ """ Tests for allmydata.util.time_format. """ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, int, list, object, range, str, max, min # noqa: F401 import time From 116f232e802c3c7c371755eeb058f4d5c29c1d6e Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 21 Jul 2020 14:08:31 -0400 Subject: [PATCH 06/22] Port time_format to Python 3. --- src/allmydata/util/_python3.py | 2 ++ src/allmydata/util/time_format.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/src/allmydata/util/_python3.py b/src/allmydata/util/_python3.py index 19bd35890..c1cfeb9ba 100644 --- a/src/allmydata/util/_python3.py +++ b/src/allmydata/util/_python3.py @@ -22,6 +22,7 @@ PORTED_MODULES = [ "allmydata.util.namespace", "allmydata.util.pollmixin", "allmydata.util._python3", + "allmydata.util.time_format", "allmydata.test.common_py3", ] @@ -29,4 +30,5 @@ PORTED_TEST_MODULES = [ "allmydata.test.test_deferredutil", "allmydata.test.test_humanreadable", "allmydata.test.test_python3", + "allmydata.test.test_time_format", ] diff --git a/src/allmydata/util/time_format.py b/src/allmydata/util/time_format.py index 27b2e0976..5807791e4 100644 --- a/src/allmydata/util/time_format.py +++ b/src/allmydata/util/time_format.py @@ -4,6 +4,15 @@ Time formatting utilities. ISO-8601: http://www.cl.cam.ac.uk/~mgk25/iso-time.html """ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, int, list, object, range, str, max, min # noqa: F401 +from future.utils import native_str import calendar, datetime, re, time @@ -18,6 +27,7 @@ def iso_utc_date(now=None, t=time.time): def iso_utc(now=None, sep='_', t=time.time): if now is None: now = t() + sep = native_str(sep) # Python 2 doesn't allow unicode input to isoformat return datetime.datetime.utcfromtimestamp(now).isoformat(sep) def iso_utc_time_to_seconds(isotime, _conversion_re=re.compile(r"(?P\d{4})-(?P\d{2})-(?P\d{2})[T_ ](?P\d{2}):(?P\d{2}):(?P\d{2})(?P\.\d+)?")): From 4018b772a35aba728af5c041fa50b474a12f2a43 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 21 Jul 2020 14:15:39 -0400 Subject: [PATCH 07/22] Port abbreviate.py. As far as I can tell, all division is already float division. --- src/allmydata/{ => test}/test_abbreviate.py | 8 ++++++++ src/allmydata/util/_python3.py | 2 ++ src/allmydata/util/abbreviate.py | 8 ++++++++ 3 files changed, 18 insertions(+) rename src/allmydata/{ => test}/test_abbreviate.py (93%) diff --git a/src/allmydata/test_abbreviate.py b/src/allmydata/test/test_abbreviate.py similarity index 93% rename from src/allmydata/test_abbreviate.py rename to src/allmydata/test/test_abbreviate.py index 483ccfda0..7a0cbbec9 100644 --- a/src/allmydata/test_abbreviate.py +++ b/src/allmydata/test/test_abbreviate.py @@ -1,6 +1,14 @@ """ Tests for allmydata.util.abbreviate. """ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, int, list, object, range, str, max, min # noqa: F401 from datetime import timedelta diff --git a/src/allmydata/util/_python3.py b/src/allmydata/util/_python3.py index c1cfeb9ba..c06f5c58b 100644 --- a/src/allmydata/util/_python3.py +++ b/src/allmydata/util/_python3.py @@ -15,6 +15,7 @@ if PY2: # Keep these sorted alphabetically, to reduce merge conflicts: PORTED_MODULES = [ + "allmydata.util.abbreviate", "allmydata.util.assertutil", "allmydata.util.deferredutil", "allmydata.util.humanreadable", @@ -27,6 +28,7 @@ PORTED_MODULES = [ ] PORTED_TEST_MODULES = [ + "allmydata.test.test_abbreviate", "allmydata.test.test_deferredutil", "allmydata.test.test_humanreadable", "allmydata.test.test_python3", diff --git a/src/allmydata/util/abbreviate.py b/src/allmydata/util/abbreviate.py index 6fdf894ce..7361ce06d 100644 --- a/src/allmydata/util/abbreviate.py +++ b/src/allmydata/util/abbreviate.py @@ -1,3 +1,11 @@ +from __future__ import division +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, int, list, object, range, str, max, min # noqa: F401 import re from datetime import timedelta From 6de05941b9a6c028ac446f2bc0d45c5d1dfdd789 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 21 Jul 2020 14:27:08 -0400 Subject: [PATCH 08/22] Remove unnecessary imports. --- src/allmydata/test/common_util.py | 2 +- src/allmydata/test/test_util.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/allmydata/test/common_util.py b/src/allmydata/test/common_util.py index 2e8bbd006..996052692 100644 --- a/src/allmydata/test/common_util.py +++ b/src/allmydata/test/common_util.py @@ -1,6 +1,6 @@ from __future__ import print_function -import os, signal, time +import os, signal from random import randrange from six.moves import StringIO diff --git a/src/allmydata/test/test_util.py b/src/allmydata/test/test_util.py index c290903a7..f041f8cc0 100644 --- a/src/allmydata/test/test_util.py +++ b/src/allmydata/test/test_util.py @@ -8,15 +8,14 @@ import yaml import gc # support PyPy from six.moves import StringIO -from datetime import timedelta from twisted.trial import unittest from twisted.internet import defer, reactor from twisted.python.failure import Failure from twisted.python import log from allmydata.util import base32, idlib, mathutil, hashutil -from allmydata.util import fileutil, abbreviate -from allmydata.util import limiter, time_format, pollmixin +from allmydata.util import fileutil +from allmydata.util import limiter, pollmixin from allmydata.util import statistics, dictutil, pipeline, yamlutil from allmydata.util import log as tahoe_log from allmydata.util.spans import Spans, overlap, DataSpans From 556869366666b924390551d6bb69ff6fc48b72db Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 21 Jul 2020 14:38:01 -0400 Subject: [PATCH 09/22] More passing tests. --- misc/python3/ratchet-passing | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/misc/python3/ratchet-passing b/misc/python3/ratchet-passing index e659c95b6..5796c0cf8 100644 --- a/misc/python3/ratchet-passing +++ b/misc/python3/ratchet-passing @@ -1,4 +1,14 @@ allmydata.test.mutable.test_exceptions.Exceptions.test_repr +allmydata.test.test_abbreviate.Abbreviate.test_abbrev_time_1s +allmydata.test.test_abbreviate.Abbreviate.test_abbrev_time_25s +allmydata.test.test_abbreviate.Abbreviate.test_abbrev_time_day +allmydata.test.test_abbreviate.Abbreviate.test_abbrev_time_future_5_minutes +allmydata.test.test_abbreviate.Abbreviate.test_abbrev_time_hours +allmydata.test.test_abbreviate.Abbreviate.test_abbrev_time_month +allmydata.test.test_abbreviate.Abbreviate.test_abbrev_time_year +allmydata.test.test_abbreviate.Abbreviate.test_parse_space +allmydata.test.test_abbreviate.Abbreviate.test_space +allmydata.test.test_abbreviate.Abbreviate.test_time allmydata.test.test_deferredutil.DeferredUtilTests.test_failure allmydata.test.test_deferredutil.DeferredUtilTests.test_gather_results allmydata.test.test_deferredutil.DeferredUtilTests.test_success @@ -11,3 +21,11 @@ allmydata.test.test_observer.Observer.test_oneshot_fireagain allmydata.test.test_python3.Python3PortingEffortTests.test_finished_porting allmydata.test.test_python3.Python3PortingEffortTests.test_ported_modules_distinct allmydata.test.test_python3.Python3PortingEffortTests.test_ported_modules_exist +allmydata.test.test_time_format.TimeFormat.test_epoch +allmydata.test.test_time_format.TimeFormat.test_epoch_in_London +allmydata.test.test_time_format.TimeFormat.test_format_delta +allmydata.test.test_time_format.TimeFormat.test_format_time +allmydata.test.test_time_format.TimeFormat.test_format_time_y2038 +allmydata.test.test_time_format.TimeFormat.test_iso_utc +allmydata.test.test_time_format.TimeFormat.test_parse_date +allmydata.test.test_time_format.TimeFormat.test_parse_duration From 51e9c2183cf15268985412481b01112f8b2a5b77 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Wed, 22 Jul 2020 10:00:15 -0400 Subject: [PATCH 10/22] Add missing file. --- src/allmydata/test/common_py3.py | 42 ++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/allmydata/test/common_py3.py diff --git a/src/allmydata/test/common_py3.py b/src/allmydata/test/common_py3.py new file mode 100644 index 000000000..e6303d2f2 --- /dev/null +++ b/src/allmydata/test/common_py3.py @@ -0,0 +1,42 @@ +""" +Common utilities that have been ported to Python 3. + +Ported to Python 3. +""" + +from __future__ import unicode_literals +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from future.utils import PY2 +if PY2: + from builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, int, list, object, range, str, max, min # noqa: F401 + +import os +import time + + +class TimezoneMixin(object): + + def setTimezone(self, timezone): + def tzset_if_possible(): + # Windows doesn't have time.tzset(). + if hasattr(time, 'tzset'): + time.tzset() + + unset = object() + originalTimezone = os.environ.get('TZ', unset) + def restoreTimezone(): + if originalTimezone is unset: + del os.environ['TZ'] + else: + os.environ['TZ'] = originalTimezone + tzset_if_possible() + + os.environ['TZ'] = timezone + self.addCleanup(restoreTimezone) + tzset_if_possible() + + def have_working_tzset(self): + return hasattr(time, 'tzset') From b4e6686211cb105f1b61154115868b2efa361fd1 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Wed, 22 Jul 2020 10:29:44 -0400 Subject: [PATCH 11/22] Port to Python 3. --- src/allmydata/test/test_observer.py | 14 ++++++++++++++ src/allmydata/util/_python3.py | 2 ++ src/allmydata/util/observer.py | 15 ++++++++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/allmydata/test/test_observer.py b/src/allmydata/test/test_observer.py index 32fd0a395..b37f0d3e1 100644 --- a/src/allmydata/test/test_observer.py +++ b/src/allmydata/test/test_observer.py @@ -1,3 +1,17 @@ +""" +Tests for allmydata.util.observer. + +Ported to Python 3. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, int, list, object, range, str, max, min # noqa: F401 from twisted.trial import unittest from twisted.internet import defer, reactor diff --git a/src/allmydata/util/_python3.py b/src/allmydata/util/_python3.py index ea4e0e702..92ad34174 100644 --- a/src/allmydata/util/_python3.py +++ b/src/allmydata/util/_python3.py @@ -22,6 +22,7 @@ PORTED_MODULES = [ "allmydata.util.humanreadable", "allmydata.util.mathutil", "allmydata.util.namespace", + "allmydata.util.observer", "allmydata.util.pollmixin", "allmydata.util._python3", ] @@ -31,6 +32,7 @@ PORTED_TEST_MODULES = [ "allmydata.test.test_base62", "allmydata.test.test_deferredutil", "allmydata.test.test_humanreadable", + "allmydata.test.test_observer", "allmydata.test.test_python3", ] diff --git a/src/allmydata/util/observer.py b/src/allmydata/util/observer.py index 30eb92329..d5003dfb3 100644 --- a/src/allmydata/util/observer.py +++ b/src/allmydata/util/observer.py @@ -1,4 +1,17 @@ -# -*- test-case-name: allmydata.test.test_observer -*- +""" +Observer for Twisted code. + +Ported to Python 3. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, int, list, object, range, str, max, min # noqa: F401 import weakref from twisted.internet import defer From 04bf9aeffcdf023c2d9a79c11bb8225f85df8eff Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Wed, 22 Jul 2020 10:30:13 -0400 Subject: [PATCH 12/22] News file. --- newsfragments/3353.minor | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 newsfragments/3353.minor diff --git a/newsfragments/3353.minor b/newsfragments/3353.minor new file mode 100644 index 000000000..e69de29bb From e427163ec84bcf4e665461e7a530a1da987c7d95 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Wed, 22 Jul 2020 10:33:23 -0400 Subject: [PATCH 13/22] Move pipeline tests into their own module. --- src/allmydata/test/test_pipeline.py | 190 ++++++++++++++++++++++++++++ src/allmydata/test/test_util.py | 178 +------------------------- 2 files changed, 191 insertions(+), 177 deletions(-) create mode 100644 src/allmydata/test/test_pipeline.py diff --git a/src/allmydata/test/test_pipeline.py b/src/allmydata/test/test_pipeline.py new file mode 100644 index 000000000..b5c0f535a --- /dev/null +++ b/src/allmydata/test/test_pipeline.py @@ -0,0 +1,190 @@ +""" +Tests for allmydata.util.pipeline. + +Ported to Python 3. +""" + +import gc + +from twisted.internet import defer +from twisted.trial import unittest +from twisted.python import log +from twisted.python.failure import Failure + +from allmydata.util import pipeline + + +class Pipeline(unittest.TestCase): + def pause(self, *args, **kwargs): + d = defer.Deferred() + self.calls.append( (d, args, kwargs) ) + return d + + def failUnlessCallsAre(self, expected): + #print self.calls + #print expected + self.failUnlessEqual(len(self.calls), len(expected), self.calls) + for i,c in enumerate(self.calls): + self.failUnlessEqual(c[1:], expected[i], str(i)) + + def test_basic(self): + self.calls = [] + finished = [] + p = pipeline.Pipeline(100) + + d = p.flush() # fires immediately + d.addCallbacks(finished.append, log.err) + self.failUnlessEqual(len(finished), 1) + finished = [] + + d = p.add(10, self.pause, "one") + # the call should start right away, and our return Deferred should + # fire right away + d.addCallbacks(finished.append, log.err) + self.failUnlessEqual(len(finished), 1) + self.failUnlessEqual(finished[0], None) + self.failUnlessCallsAre([ ( ("one",) , {} ) ]) + self.failUnlessEqual(p.gauge, 10) + + # pipeline: [one] + + finished = [] + d = p.add(20, self.pause, "two", kw=2) + # pipeline: [one, two] + + # the call and the Deferred should fire right away + d.addCallbacks(finished.append, log.err) + self.failUnlessEqual(len(finished), 1) + self.failUnlessEqual(finished[0], None) + self.failUnlessCallsAre([ ( ("one",) , {} ), + ( ("two",) , {"kw": 2} ), + ]) + self.failUnlessEqual(p.gauge, 30) + + self.calls[0][0].callback("one-result") + # pipeline: [two] + self.failUnlessEqual(p.gauge, 20) + + finished = [] + d = p.add(90, self.pause, "three", "posarg1") + # pipeline: [two, three] + flushed = [] + fd = p.flush() + fd.addCallbacks(flushed.append, log.err) + self.failUnlessEqual(flushed, []) + + # the call will be made right away, but the return Deferred will not, + # because the pipeline is now full. + d.addCallbacks(finished.append, log.err) + self.failUnlessEqual(len(finished), 0) + self.failUnlessCallsAre([ ( ("one",) , {} ), + ( ("two",) , {"kw": 2} ), + ( ("three", "posarg1"), {} ), + ]) + self.failUnlessEqual(p.gauge, 110) + + self.failUnlessRaises(pipeline.SingleFileError, p.add, 10, self.pause) + + # retiring either call will unblock the pipeline, causing the #3 + # Deferred to fire + self.calls[2][0].callback("three-result") + # pipeline: [two] + + self.failUnlessEqual(len(finished), 1) + self.failUnlessEqual(finished[0], None) + self.failUnlessEqual(flushed, []) + + # retiring call#2 will finally allow the flush() Deferred to fire + self.calls[1][0].callback("two-result") + self.failUnlessEqual(len(flushed), 1) + + def test_errors(self): + self.calls = [] + p = pipeline.Pipeline(100) + + d1 = p.add(200, self.pause, "one") + d2 = p.flush() + + finished = [] + d1.addBoth(finished.append) + self.failUnlessEqual(finished, []) + + flushed = [] + d2.addBoth(flushed.append) + self.failUnlessEqual(flushed, []) + + self.calls[0][0].errback(ValueError("oops")) + + self.failUnlessEqual(len(finished), 1) + f = finished[0] + self.failUnless(isinstance(f, Failure)) + self.failUnless(f.check(pipeline.PipelineError)) + self.failUnlessIn("PipelineError", str(f.value)) + self.failUnlessIn("ValueError", str(f.value)) + r = repr(f.value) + self.failUnless("ValueError" in r, r) + f2 = f.value.error + self.failUnless(f2.check(ValueError)) + + self.failUnlessEqual(len(flushed), 1) + f = flushed[0] + self.failUnless(isinstance(f, Failure)) + self.failUnless(f.check(pipeline.PipelineError)) + f2 = f.value.error + self.failUnless(f2.check(ValueError)) + + # now that the pipeline is in the failed state, any new calls will + # fail immediately + + d3 = p.add(20, self.pause, "two") + + finished = [] + d3.addBoth(finished.append) + self.failUnlessEqual(len(finished), 1) + f = finished[0] + self.failUnless(isinstance(f, Failure)) + self.failUnless(f.check(pipeline.PipelineError)) + r = repr(f.value) + self.failUnless("ValueError" in r, r) + f2 = f.value.error + self.failUnless(f2.check(ValueError)) + + d4 = p.flush() + flushed = [] + d4.addBoth(flushed.append) + self.failUnlessEqual(len(flushed), 1) + f = flushed[0] + self.failUnless(isinstance(f, Failure)) + self.failUnless(f.check(pipeline.PipelineError)) + f2 = f.value.error + self.failUnless(f2.check(ValueError)) + + def test_errors2(self): + self.calls = [] + p = pipeline.Pipeline(100) + + d1 = p.add(10, self.pause, "one") + d2 = p.add(20, self.pause, "two") + d3 = p.add(30, self.pause, "three") + d4 = p.flush() + + # one call fails, then the second one succeeds: make sure + # ExpandableDeferredList tolerates the second one + + flushed = [] + d4.addBoth(flushed.append) + self.failUnlessEqual(flushed, []) + + self.calls[0][0].errback(ValueError("oops")) + self.failUnlessEqual(len(flushed), 1) + f = flushed[0] + self.failUnless(isinstance(f, Failure)) + self.failUnless(f.check(pipeline.PipelineError)) + f2 = f.value.error + self.failUnless(f2.check(ValueError)) + + self.calls[1][0].callback("two-result") + self.calls[2][0].errback(ValueError("three-error")) + + del d1,d2,d3,d4 + gc.collect() # for PyPy diff --git a/src/allmydata/test/test_util.py b/src/allmydata/test/test_util.py index f744feb7e..a71600627 100644 --- a/src/allmydata/test/test_util.py +++ b/src/allmydata/test/test_util.py @@ -5,19 +5,17 @@ import six import hashlib import os, time, sys import yaml -import gc # support PyPy from six.moves import StringIO from datetime import timedelta from twisted.trial import unittest from twisted.internet import defer, reactor from twisted.python.failure import Failure -from twisted.python import log from allmydata.util import base32, idlib, mathutil, hashutil from allmydata.util import fileutil, abbreviate from allmydata.util import limiter, time_format, pollmixin -from allmydata.util import statistics, dictutil, pipeline, yamlutil +from allmydata.util import statistics, dictutil, yamlutil from allmydata.util import log as tahoe_log from allmydata.util.spans import Spans, overlap, DataSpans from allmydata.util.fileutil import EncryptedTemporaryFile @@ -1121,180 +1119,6 @@ class DictUtil(unittest.TestCase): self.failUnlessEqual(d["one"], 1) self.failUnlessEqual(d.get_aux("one"), None) -class Pipeline(unittest.TestCase): - def pause(self, *args, **kwargs): - d = defer.Deferred() - self.calls.append( (d, args, kwargs) ) - return d - - def failUnlessCallsAre(self, expected): - #print self.calls - #print expected - self.failUnlessEqual(len(self.calls), len(expected), self.calls) - for i,c in enumerate(self.calls): - self.failUnlessEqual(c[1:], expected[i], str(i)) - - def test_basic(self): - self.calls = [] - finished = [] - p = pipeline.Pipeline(100) - - d = p.flush() # fires immediately - d.addCallbacks(finished.append, log.err) - self.failUnlessEqual(len(finished), 1) - finished = [] - - d = p.add(10, self.pause, "one") - # the call should start right away, and our return Deferred should - # fire right away - d.addCallbacks(finished.append, log.err) - self.failUnlessEqual(len(finished), 1) - self.failUnlessEqual(finished[0], None) - self.failUnlessCallsAre([ ( ("one",) , {} ) ]) - self.failUnlessEqual(p.gauge, 10) - - # pipeline: [one] - - finished = [] - d = p.add(20, self.pause, "two", kw=2) - # pipeline: [one, two] - - # the call and the Deferred should fire right away - d.addCallbacks(finished.append, log.err) - self.failUnlessEqual(len(finished), 1) - self.failUnlessEqual(finished[0], None) - self.failUnlessCallsAre([ ( ("one",) , {} ), - ( ("two",) , {"kw": 2} ), - ]) - self.failUnlessEqual(p.gauge, 30) - - self.calls[0][0].callback("one-result") - # pipeline: [two] - self.failUnlessEqual(p.gauge, 20) - - finished = [] - d = p.add(90, self.pause, "three", "posarg1") - # pipeline: [two, three] - flushed = [] - fd = p.flush() - fd.addCallbacks(flushed.append, log.err) - self.failUnlessEqual(flushed, []) - - # the call will be made right away, but the return Deferred will not, - # because the pipeline is now full. - d.addCallbacks(finished.append, log.err) - self.failUnlessEqual(len(finished), 0) - self.failUnlessCallsAre([ ( ("one",) , {} ), - ( ("two",) , {"kw": 2} ), - ( ("three", "posarg1"), {} ), - ]) - self.failUnlessEqual(p.gauge, 110) - - self.failUnlessRaises(pipeline.SingleFileError, p.add, 10, self.pause) - - # retiring either call will unblock the pipeline, causing the #3 - # Deferred to fire - self.calls[2][0].callback("three-result") - # pipeline: [two] - - self.failUnlessEqual(len(finished), 1) - self.failUnlessEqual(finished[0], None) - self.failUnlessEqual(flushed, []) - - # retiring call#2 will finally allow the flush() Deferred to fire - self.calls[1][0].callback("two-result") - self.failUnlessEqual(len(flushed), 1) - - def test_errors(self): - self.calls = [] - p = pipeline.Pipeline(100) - - d1 = p.add(200, self.pause, "one") - d2 = p.flush() - - finished = [] - d1.addBoth(finished.append) - self.failUnlessEqual(finished, []) - - flushed = [] - d2.addBoth(flushed.append) - self.failUnlessEqual(flushed, []) - - self.calls[0][0].errback(ValueError("oops")) - - self.failUnlessEqual(len(finished), 1) - f = finished[0] - self.failUnless(isinstance(f, Failure)) - self.failUnless(f.check(pipeline.PipelineError)) - self.failUnlessIn("PipelineError", str(f.value)) - self.failUnlessIn("ValueError", str(f.value)) - r = repr(f.value) - self.failUnless("ValueError" in r, r) - f2 = f.value.error - self.failUnless(f2.check(ValueError)) - - self.failUnlessEqual(len(flushed), 1) - f = flushed[0] - self.failUnless(isinstance(f, Failure)) - self.failUnless(f.check(pipeline.PipelineError)) - f2 = f.value.error - self.failUnless(f2.check(ValueError)) - - # now that the pipeline is in the failed state, any new calls will - # fail immediately - - d3 = p.add(20, self.pause, "two") - - finished = [] - d3.addBoth(finished.append) - self.failUnlessEqual(len(finished), 1) - f = finished[0] - self.failUnless(isinstance(f, Failure)) - self.failUnless(f.check(pipeline.PipelineError)) - r = repr(f.value) - self.failUnless("ValueError" in r, r) - f2 = f.value.error - self.failUnless(f2.check(ValueError)) - - d4 = p.flush() - flushed = [] - d4.addBoth(flushed.append) - self.failUnlessEqual(len(flushed), 1) - f = flushed[0] - self.failUnless(isinstance(f, Failure)) - self.failUnless(f.check(pipeline.PipelineError)) - f2 = f.value.error - self.failUnless(f2.check(ValueError)) - - def test_errors2(self): - self.calls = [] - p = pipeline.Pipeline(100) - - d1 = p.add(10, self.pause, "one") - d2 = p.add(20, self.pause, "two") - d3 = p.add(30, self.pause, "three") - d4 = p.flush() - - # one call fails, then the second one succeeds: make sure - # ExpandableDeferredList tolerates the second one - - flushed = [] - d4.addBoth(flushed.append) - self.failUnlessEqual(flushed, []) - - self.calls[0][0].errback(ValueError("oops")) - self.failUnlessEqual(len(flushed), 1) - f = flushed[0] - self.failUnless(isinstance(f, Failure)) - self.failUnless(f.check(pipeline.PipelineError)) - f2 = f.value.error - self.failUnless(f2.check(ValueError)) - - self.calls[1][0].callback("two-result") - self.calls[2][0].errback(ValueError("three-error")) - - del d1,d2,d3,d4 - gc.collect() # for PyPy class SampleError(Exception): pass From 0763f9f90b4ae7549efac5d6752d3a1e7bb058f6 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Wed, 22 Jul 2020 10:36:50 -0400 Subject: [PATCH 14/22] Port to Python 3. --- src/allmydata/test/test_pipeline.py | 8 ++++++++ src/allmydata/util/_python3.py | 2 ++ src/allmydata/util/pipeline.py | 15 +++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/src/allmydata/test/test_pipeline.py b/src/allmydata/test/test_pipeline.py index b5c0f535a..ab7059521 100644 --- a/src/allmydata/test/test_pipeline.py +++ b/src/allmydata/test/test_pipeline.py @@ -3,6 +3,14 @@ Tests for allmydata.util.pipeline. Ported to Python 3. """ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, int, list, object, range, str, max, min # noqa: F401 import gc diff --git a/src/allmydata/util/_python3.py b/src/allmydata/util/_python3.py index 92ad34174..72e56581b 100644 --- a/src/allmydata/util/_python3.py +++ b/src/allmydata/util/_python3.py @@ -23,6 +23,7 @@ PORTED_MODULES = [ "allmydata.util.mathutil", "allmydata.util.namespace", "allmydata.util.observer", + "allmydata.util.pipeline", "allmydata.util.pollmixin", "allmydata.util._python3", ] @@ -33,6 +34,7 @@ PORTED_TEST_MODULES = [ "allmydata.test.test_deferredutil", "allmydata.test.test_humanreadable", "allmydata.test.test_observer", + "allmydata.test.test_pipeline", "allmydata.test.test_python3", ] diff --git a/src/allmydata/util/pipeline.py b/src/allmydata/util/pipeline.py index 285a06b98..df80e2c6c 100644 --- a/src/allmydata/util/pipeline.py +++ b/src/allmydata/util/pipeline.py @@ -1,9 +1,24 @@ +""" +A pipeline of Deferreds. + +Ported to Python 3. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from future.utils import PY2 +if PY2: + from builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, int, list, object, range, str, max, min # noqa: F401 from twisted.internet import defer from twisted.python.failure import Failure from twisted.python import log from allmydata.util.assertutil import precondition + class PipelineError(Exception): """One of the pipelined messages returned an error. The received Failure object is stored in my .error attribute.""" From 691322764d3d71a4beba842dcf6ea95868f9ae6a Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Wed, 22 Jul 2020 10:37:30 -0400 Subject: [PATCH 15/22] Ratchet tests. --- misc/python3/ratchet-passing | 3 +++ 1 file changed, 3 insertions(+) diff --git a/misc/python3/ratchet-passing b/misc/python3/ratchet-passing index a6e1de68b..08f9b3fe9 100644 --- a/misc/python3/ratchet-passing +++ b/misc/python3/ratchet-passing @@ -23,6 +23,9 @@ allmydata.test.test_observer.Observer.test_lazy_oneshot allmydata.test.test_observer.Observer.test_observerlist allmydata.test.test_observer.Observer.test_oneshot allmydata.test.test_observer.Observer.test_oneshot_fireagain +allmydata.test.test_pipeline.Pipeline.test_basic +allmydata.test.test_pipeline.Pipeline.test_errors +allmydata.test.test_pipeline.Pipeline.test_errors2 allmydata.test.test_python3.Python3PortingEffortTests.test_finished_porting allmydata.test.test_python3.Python3PortingEffortTests.test_ported_modules_distinct allmydata.test.test_python3.Python3PortingEffortTests.test_ported_modules_exist From e48aecfa1a346c1e344bf5fa0ac67df388dda503 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Wed, 22 Jul 2020 12:51:18 -0400 Subject: [PATCH 16/22] Move parsing tests to better location, and fix them. --- src/allmydata/test/test_storage.py | 19 ------------------- src/allmydata/test/test_time_format.py | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index 0acf60152..6867adc3b 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -3852,25 +3852,6 @@ class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin): expiration_mode="bogus") self.failUnlessIn("GC mode 'bogus' must be 'age' or 'cutoff-date'", str(e)) - def test_parse_duration(self): - DAY = 24*60*60 - MONTH = 31*DAY - YEAR = 365*DAY - p = time_format.parse_duration - self.failUnlessEqual(p("7days"), 7*DAY) - self.failUnlessEqual(p("31day"), 31*DAY) - self.failUnlessEqual(p("60 days"), 60*DAY) - self.failUnlessEqual(p("2mo"), 2*MONTH) - self.failUnlessEqual(p("3 month"), 3*MONTH) - self.failUnlessEqual(p("2years"), 2*YEAR) - e = self.failUnlessRaises(ValueError, p, "2kumquats") - self.failUnlessIn("no unit (like day, month, or year) in '2kumquats'", str(e)) - - def test_parse_date(self): - p = time_format.parse_date - self.failUnless(isinstance(p("2009-03-18"), int), p("2009-03-18")) - self.failUnlessEqual(p("2009-03-18"), 1237334400) - def test_limited_history(self): basedir = "storage/LeaseCrawler/limited_history" fileutil.make_dirs(basedir) diff --git a/src/allmydata/test/test_time_format.py b/src/allmydata/test/test_time_format.py index 54593e5b9..5f2c3ad46 100644 --- a/src/allmydata/test/test_time_format.py +++ b/src/allmydata/test/test_time_format.py @@ -10,6 +10,8 @@ from future.utils import PY2 if PY2: from builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, int, list, object, range, str, max, min # noqa: F401 +from past.builtins import long + import time from twisted.trial import unittest @@ -87,6 +89,8 @@ class TimeFormat(unittest.TestCase, TimezoneMixin): def test_parse_duration(self): p = time_format.parse_duration DAY = 24*60*60 + MONTH = 31*DAY + YEAR = 365*DAY self.failUnlessEqual(p("1 day"), DAY) self.failUnlessEqual(p("2 days"), 2*DAY) self.failUnlessEqual(p("3 months"), 3*31*DAY) @@ -95,9 +99,20 @@ class TimeFormat(unittest.TestCase, TimezoneMixin): e = self.failUnlessRaises(ValueError, p, "123") self.failUnlessIn("no unit (like day, month, or year) in '123'", str(e)) + self.failUnlessEqual(p("7days"), 7*DAY) + self.failUnlessEqual(p("31day"), 31*DAY) + self.failUnlessEqual(p("60 days"), 60*DAY) + self.failUnlessEqual(p("2mo"), 2*MONTH) + self.failUnlessEqual(p("3 month"), 3*MONTH) + self.failUnlessEqual(p("2years"), 2*YEAR) + e = self.failUnlessRaises(ValueError, p, "2kumquats") + self.failUnlessIn("no unit (like day, month, or year) in '2kumquats'", str(e)) def test_parse_date(self): - self.failUnlessEqual(time_format.parse_date("2010-02-21"), 1266710400) + p = time_format.parse_date + self.failUnlessEqual(p("2010-02-21"), 1266710400) + self.failUnless(isinstance(p("2009-03-18"), (int, long)), p("2009-03-18")) + self.failUnlessEqual(p("2009-03-18"), 1237334400) def test_format_time(self): self.failUnlessEqual(time_format.format_time(time.gmtime(0)), '1970-01-01 00:00:00') From eb688dfd7e491b405a85491321161d234e74501a Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Wed, 22 Jul 2020 13:22:50 -0400 Subject: [PATCH 17/22] Fix lint. --- src/allmydata/test/test_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py index 6867adc3b..ccf9fc5cb 100644 --- a/src/allmydata/test/test_storage.py +++ b/src/allmydata/test/test_storage.py @@ -26,7 +26,7 @@ from zope.interface import implementer from foolscap.api import fireEventually import itertools from allmydata import interfaces -from allmydata.util import fileutil, hashutil, base32, pollmixin, time_format +from allmydata.util import fileutil, hashutil, base32, pollmixin from allmydata.storage.server import StorageServer from allmydata.storage.mutable import MutableShareFile from allmydata.storage.immutable import BucketWriter, BucketReader From 31ed6b0a4fa722e4810689f1d2dad604b7184cf6 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 23 Jul 2020 11:37:56 -0400 Subject: [PATCH 18/22] News fragment. --- newsfragments/3346.minor | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 newsfragments/3346.minor diff --git a/newsfragments/3346.minor b/newsfragments/3346.minor new file mode 100644 index 000000000..e69de29bb From b0083e342b3095473fd1de23f643c50c50fdbf01 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 23 Jul 2020 11:47:06 -0400 Subject: [PATCH 19/22] Document being ported. --- src/allmydata/test/test_abbreviate.py | 2 ++ src/allmydata/util/abbreviate.py | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/src/allmydata/test/test_abbreviate.py b/src/allmydata/test/test_abbreviate.py index 7a0cbbec9..958c36742 100644 --- a/src/allmydata/test/test_abbreviate.py +++ b/src/allmydata/test/test_abbreviate.py @@ -1,5 +1,7 @@ """ Tests for allmydata.util.abbreviate. + +Ported to Python 3. """ from __future__ import absolute_import from __future__ import division diff --git a/src/allmydata/util/abbreviate.py b/src/allmydata/util/abbreviate.py index 7361ce06d..95d547836 100644 --- a/src/allmydata/util/abbreviate.py +++ b/src/allmydata/util/abbreviate.py @@ -1,3 +1,8 @@ +""" +Convert timestamps to abbreviated English text. + +Ported to Python 3. +""" from __future__ import division from __future__ import absolute_import from __future__ import print_function From cab1b02ba2b01b3dec53edc5fe408eecce15d552 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 23 Jul 2020 11:47:12 -0400 Subject: [PATCH 20/22] Use constants, since we have them. --- src/allmydata/test/test_time_format.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/allmydata/test/test_time_format.py b/src/allmydata/test/test_time_format.py index 5f2c3ad46..fa68ccc36 100644 --- a/src/allmydata/test/test_time_format.py +++ b/src/allmydata/test/test_time_format.py @@ -93,9 +93,9 @@ class TimeFormat(unittest.TestCase, TimezoneMixin): YEAR = 365*DAY self.failUnlessEqual(p("1 day"), DAY) self.failUnlessEqual(p("2 days"), 2*DAY) - self.failUnlessEqual(p("3 months"), 3*31*DAY) - self.failUnlessEqual(p("4 mo"), 4*31*DAY) - self.failUnlessEqual(p("5 years"), 5*365*DAY) + self.failUnlessEqual(p("3 months"), 3*MONTH) + self.failUnlessEqual(p("4 mo"), 4*MONTH) + self.failUnlessEqual(p("5 years"), 5*YEAR) e = self.failUnlessRaises(ValueError, p, "123") self.failUnlessIn("no unit (like day, month, or year) in '123'", str(e)) From ab4393b50ecc3fc9a1c81804fc71144f2eee44bd Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Thu, 23 Jul 2020 11:49:02 -0400 Subject: [PATCH 21/22] Document unicode. --- src/allmydata/util/abbreviate.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/allmydata/util/abbreviate.py b/src/allmydata/util/abbreviate.py index 95d547836..e7bdd8410 100644 --- a/src/allmydata/util/abbreviate.py +++ b/src/allmydata/util/abbreviate.py @@ -22,6 +22,10 @@ MONTH = 30*DAY YEAR = 365*DAY def abbreviate_time(s): + """ + Given time in seconds (float or int) or timedelta, summarize as English by + returning unicode string. + """ postfix = '' if isinstance(s, timedelta): # this feels counter-intuitive that positive numbers in a @@ -58,6 +62,9 @@ def abbreviate_time(s): return _plural(s / YEAR, "year") def abbreviate_space(s, SI=True): + """ + Given size in bytes summarize as English by returning unicode string. + """ if s is None: return "unknown" if SI: From 38648c0f8fb9e7bb6e56d2a5aa4cc0ba5382f04b Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 24 Jul 2020 11:09:08 -0400 Subject: [PATCH 22/22] Fix indentation --- src/allmydata/util/_python3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allmydata/util/_python3.py b/src/allmydata/util/_python3.py index 8c59978c6..08c11b3a6 100644 --- a/src/allmydata/util/_python3.py +++ b/src/allmydata/util/_python3.py @@ -38,7 +38,7 @@ PORTED_TEST_MODULES = [ "allmydata.test.test_hashtree", "allmydata.test.test_hashutil", "allmydata.test.test_humanreadable", - "allmydata.test.test_netstring", + "allmydata.test.test_netstring", "allmydata.test.test_observer", "allmydata.test.test_pipeline", "allmydata.test.test_python3",