Exemplo n.º 1
0
 def test_constraints(self, ports):
     a, b = ports
     self.expectThat(a, GreaterThan(0))
     self.expectThat(a, LessThan(65536))
     self.expectThat(b, GreaterThan(0))
     self.expectThat(b, LessThan(65536))
     self.expectThat(a, Not(Equals(b)))
    def test_sample_binary_packages__constant_number_sql_queries(self):
        # Retrieving
        # DistributionSourcePackageRelease.sample_binary_packages and
        # accessing the property "summary" of its items requires a
        # constant number of SQL queries, regardless of the number
        # of existing binary package releases.
        self.makeBinaryPackageRelease()
        self.updateDistroSeriesPackageCache()
        with StormStatementRecorder() as recorder:
            for ds_package in self.dsp_release.sample_binary_packages:
                ds_package.summary
        self.assertThat(recorder, HasQueryCount(LessThan(5)))
        self.assertEqual(1, self.dsp_release.sample_binary_packages.count())

        for iteration in range(5):
            self.makeBinaryPackageRelease()
        self.updateDistroSeriesPackageCache()
        with StormStatementRecorder() as recorder:
            for ds_package in self.dsp_release.sample_binary_packages:
                ds_package.summary
        self.assertThat(recorder, HasQueryCount(LessThan(5)))
        self.assertEqual(6, self.dsp_release.sample_binary_packages.count())

        # Even if the cache is not updated for binary packages,
        # DistributionSourcePackageRelease objects do not try to
        # retrieve DistroSeriesPackageCache records if they know
        # that such records do not exist.
        for iteration in range(5):
            self.makeBinaryPackageRelease()
        with StormStatementRecorder() as recorder:
            for ds_package in self.dsp_release.sample_binary_packages:
                ds_package.summary
        self.assertThat(recorder, HasQueryCount(LessThan(5)))
        self.assertEqual(11, self.dsp_release.sample_binary_packages.count())
Exemplo n.º 3
0
 def test_rough_length_decorated_result_set(self):
     # StormRangeFactory.rough_length can handle DecoratedResultSets.
     resultset = self.makeDecoratedStormResultSet()
     range_factory = StormRangeFactory(resultset)
     estimated_length = range_factory.rough_length
     self.assertThat(estimated_length, LessThan(10))
     self.assertThat(estimated_length, Not(LessThan(1)))
Exemplo n.º 4
0
    def test_getPrecachedPersonsFromIDs(self):
        # The getPrecachedPersonsFromIDs() method should only make one
        # query to load all the extraneous data. Accessing the
        # attributes should then cause zero queries.
        person_ids = [self.factory.makePerson().id for i in range(3)]

        with StormStatementRecorder() as recorder:
            persons = list(
                self.person_set.getPrecachedPersonsFromIDs(
                    person_ids,
                    need_karma=True,
                    need_ubuntu_coc=True,
                    need_location=True,
                    need_archive=True,
                    need_preferred_email=True,
                    need_validity=True))
        self.assertThat(recorder, HasQueryCount(LessThan(2)))

        with StormStatementRecorder() as recorder:
            for person in persons:
                person.is_valid_person
                person.karma
                person.is_ubuntu_coc_signer
                person.location,
                person.archive
                person.preferredemail
        self.assertThat(recorder, HasQueryCount(LessThan(1)))
Exemplo n.º 5
0
 def test_mismatch(self):
     matcher = HasQueryCount(LessThan(2))
     collector = RequestTimelineCollector()
     collector.count = 2
     collector.queries = [
         (0, 1, "SQL-main-slave", "SELECT 1 FROM Person", None),
         (2, 3, "SQL-main-slave", "SELECT 1 FROM Product", None),
         ]
     mismatch = matcher.match(collector)
     self.assertThat(mismatch, Not(Is(None)))
     details = mismatch.get_details()
     lines = []
     for name, content in details.items():
         self.assertEqual("queries", name)
         self.assertEqual("text", content.content_type.type)
         lines.append(''.join(content.iter_text()))
     separator = "-" * 70
     expected_lines = [
         "0-1@SQL-main-slave SELECT 1 FROM Person\n" + separator + "\n" +
         "2-3@SQL-main-slave SELECT 1 FROM Product\n" + separator,
         ]
     self.assertEqual(expected_lines, lines)
     self.assertEqual(
         "queries do not match: %s" % (LessThan(2).match(2).describe(),),
         mismatch.describe())
Exemplo n.º 6
0
    def test_run_environment(self, run_mock):
        plugin = colcon.ColconPlugin("test-part", self.properties,
                                     self.project)

        with mock.patch.object(plugin,
                               "_source_setup_sh",
                               wraps=plugin._source_setup_sh) as sh_mock:
            # Joining and re-splitting to get hacked script in there as well
            environment = "\n".join(plugin.env(plugin.installdir)).split("\n")
            sh_mock.assert_called_with(plugin.installdir)

        underlay_setup = os.path.join(plugin.options.colcon_rosdistro,
                                      "setup.sh")
        overlay_setup = os.path.join("snap", "setup.sh")

        # Verify that LD_LIBRARY_PATH was set before any setup.sh is sourced. Also
        # verify that the underlay setup is sourced before the overlay.
        ld_library_path_index = [
            i for i, line in enumerate(environment)
            if "LD_LIBRARY_PATH" in line
        ][0]
        underlay_source_setup_index = [
            i for i, line in enumerate(environment) if underlay_setup in line
        ][0]
        overlay_source_setup_index = [
            i for i, line in enumerate(environment) if overlay_setup in line
        ][0]
        self.assertThat(ld_library_path_index,
                        LessThan(underlay_source_setup_index))
        self.assertThat(underlay_source_setup_index,
                        LessThan(overlay_source_setup_index))
Exemplo n.º 7
0
    def test_set_diffing_smart(self):
        """
        Small modifications to sets have diffs that are small. Their reverse
        is also small.
        """
        # Any Application with a large set of ports will do, just use
        # hypothesis for convenience of generating a large number of ports on
        # an application.
        application = application_strategy(min_number_of_ports=1000).example()

        new_ports = list(
            Port(internal_port=i, external_port=i) for i in xrange(4))
        a = reduce(lambda x, y: x.transform(['ports'], lambda x: x.add(y)),
                   new_ports, application)
        encoded_application = wire_encode(application)

        diff = create_diff(application, a)
        encoded_diff = wire_encode(diff)
        self.assertThat(len(encoded_diff),
                        LessThan(len(encoded_application) / 2))
        self.assertThat(
            wire_decode(encoded_diff).apply(application), Equals(a))

        removal_diff = create_diff(a, application)
        encoded_removal_diff = wire_encode(removal_diff)
        self.assertThat(len(encoded_removal_diff),
                        LessThan(len(encoded_application) / 2))
        self.assertThat(
            wire_decode(encoded_removal_diff).apply(a), Equals(application))
Exemplo n.º 8
0
    def test_deployment_diffing_smart(self):
        """
        Small modifications to a deployment have diffs that are small. Their
        reverse is also small.
        """
        # Any large deployment will do, just use hypothesis for convenience of
        # generating a large deployment.
        deployment = deployment_strategy(min_number_of_nodes=90).example()

        new_nodes = list(Node(uuid=uuid4()) for _ in xrange(4))
        d = reduce(lambda x, y: x.update_node(y), new_nodes, deployment)
        encoded_deployment = wire_encode(deployment)

        diff = create_diff(deployment, d)
        encoded_diff = wire_encode(diff)
        self.assertThat(len(encoded_diff),
                        LessThan(len(encoded_deployment) / 2))
        self.assertThat(wire_decode(encoded_diff).apply(deployment), Equals(d))

        removal_diff = create_diff(d, deployment)
        encoded_removal_diff = wire_encode(removal_diff)
        self.assertThat(len(encoded_removal_diff),
                        LessThan(len(encoded_deployment) / 2))
        self.assertThat(
            wire_decode(encoded_removal_diff).apply(d), Equals(deployment))
Exemplo n.º 9
0
class TestAllMatch(TestCase, TestMatchersInterface):

    matches_matcher = AllMatch(LessThan(10))
    matches_matches = [
        [9, 9, 9],
        (9, 9),
        iter([9, 9, 9, 9, 9]),
        ]
    matches_mismatches = [
        [11, 9, 9],
        iter([9, 12, 9, 11]),
        ]

    str_examples = [
        ("AllMatch(LessThan(12))", AllMatch(LessThan(12))),
        ]

    describe_examples = [
        ('Differences: [\n'
         '10 is not > 11\n'
         '10 is not > 10\n'
         ']',
         [11, 9, 10],
         AllMatch(LessThan(10))),
        ]
Exemplo n.º 10
0
 def test_with_backtrace(self):
     matcher = HasQueryCount(LessThan(2))
     collector = RequestTimelineCollector()
     collector.count = 2
     collector.queries = [
         (0, 1, "SQL-main-slave", "SELECT 1 FROM Person",
          '  File "example", line 2, in <module>\n'
          '    Store.of(Person).one()\n'),
         (2, 3, "SQL-main-slave", "SELECT 1 FROM Product",
          '  File "example", line 3, in <module>\n'
          '    Store.of(Product).one()\n'),
         ]
     mismatch = matcher.match(collector)
     self.assertThat(mismatch, Not(Is(None)))
     details = mismatch.get_details()
     lines = []
     for name, content in details.items():
         self.assertEqual("queries", name)
         self.assertEqual("text", content.content_type.type)
         lines.append(''.join(content.iter_text()))
     separator = "-" * 70
     backtrace_separator = "." * 70
     expected_lines = [
         '0-1@SQL-main-slave SELECT 1 FROM Person\n' + separator + '\n' +
         '  File "example", line 2, in <module>\n' +
         '    Store.of(Person).one()\n' + backtrace_separator + '\n' +
         '2-3@SQL-main-slave SELECT 1 FROM Product\n' + separator + '\n' +
         '  File "example", line 3, in <module>\n' +
         '    Store.of(Product).one()\n' + backtrace_separator,
         ]
     self.assertEqual(expected_lines, lines)
     self.assertEqual(
         "queries do not match: %s" % (LessThan(2).match(2).describe(),),
         mismatch.describe())
Exemplo n.º 11
0
 def test_rough_length_distinct_query(self):
     # StormRangeFactory.rough_length with SELECT DISTINCT queries.
     resultset = self.makeStormResultSet()
     resultset.config(distinct=True)
     resultset.order_by(Person.name, Person.id)
     range_factory = StormRangeFactory(resultset)
     estimated_length = range_factory.rough_length
     self.assertThat(estimated_length, LessThan(10))
     self.assertThat(estimated_length, Not(LessThan(1)))
Exemplo n.º 12
0
 def test_rough_length_first_sort_column_desc(self):
     # StormRangeFactory.rough_length can handle result sets where
     # the first sort column has descendig order.
     resultset = self.makeStormResultSet()
     resultset.order_by(Desc(Person.id))
     range_factory = StormRangeFactory(resultset)
     estimated_length = range_factory.rough_length
     self.assertThat(estimated_length, LessThan(10))
     self.assertThat(estimated_length, Not(LessThan(1)))
Exemplo n.º 13
0
 def test_rough_length(self):
     # StormRangeFactory.rough_length returns an estimate of the
     # length of the result set.
     resultset = self.makeStormResultSet()
     resultset.order_by(Person.id)
     range_factory = StormRangeFactory(resultset)
     estimated_length = range_factory.rough_length
     self.assertThat(estimated_length, LessThan(10))
     self.assertThat(estimated_length, Not(LessThan(1)))
Exemplo n.º 14
0
 def assertRaisesFailure(self, failure, function, *args, **kwargs):
     try:
         function(*args, **kwargs)
     except Failure as raised_failure:
         self.assertThat(sys.version_info, LessThan((3, 0)))
         self.assertEqual(failure, raised_failure)
     except Exception as raised_exception:
         self.assertThat(sys.version_info, Not(LessThan((3, 0))))
         self.assertEqual(failure.value, raised_exception)
Exemplo n.º 15
0
class TestLessThanInterface(TestCase, TestMatchersInterface):

    matches_matcher = LessThan(4)
    matches_matches = [-5, 3]
    matches_mismatches = [4, 5, 5000]

    str_examples = [
        ("LessThan(12)", LessThan(12)),
    ]

    describe_examples = [('4 is >= 4', 4, LessThan(4))]
    def test_window_geometry(self):
        """Window.geometry property

        Check that all Window geometry properties work and have a plausible
        range.
        """
        # ensure we have at least one open app window
        self.start_mock_app(EmulatorBase)

        display = Display.create()
        top = left = right = bottom = None
        # for multi-monitor setups, make sure we examine the full desktop
        # space:
        for monitor in range(display.get_num_screens()):
            sx, sy, swidth, sheight = Display.create().get_screen_geometry(
                monitor
            )
            logger.info(
                "Monitor %d geometry is (%d, %d, %d, %d)",
                monitor,
                sx,
                sy,
                swidth,
                sheight,
            )
            if left is None or sx < left:
                left = sx
            if top is None or sy < top:
                top = sy
            if right is None or sx + swidth >= right:
                right = sx + swidth
            if bottom is None or sy + sheight >= bottom:
                bottom = sy + sheight

        logger.info(
            "Total desktop geometry is (%d, %d), (%d, %d)",
            left,
            top,
            right,
            bottom,
        )
        for win in self.process_manager.get_open_windows():
            logger.info("Win '%r' geometry is %r", win, win.geometry)
            geom = win.geometry
            self.assertThat(len(geom), Equals(4))
            self.assertThat(geom[0], GreaterThan(left - 1))  # no GreaterEquals
            self.assertThat(geom[1], GreaterThan(top - 1))
            self.assertThat(geom[2], LessThan(right))
            self.assertThat(geom[3], LessThan(bottom))
Exemplo n.º 17
0
    def test_getEnvironment(self):
        bc = BaseConfiguration()
        self.assertEqual(10, bc.getEnvironment(10))
        self.assertEqual(20, bc.getEnvironment(20))
        self.assertEqual(30, bc.getEnvironment(30))

        self.assertEqual(10, bc.getEnvironment("production"))
        self.assertEqual(20, bc.getEnvironment("staging"))
        self.assertEqual(30, bc.getEnvironment("development"))

        self.assertThat(bc.getEnvironment("production"),
                        LessThan(bc.getEnvironment("staging")))

        self.assertThat(bc.getEnvironment("staging"),
                        LessThan(bc.getEnvironment("development")))
 def test_messages_query_counts_constant(self):
     # XXX Robert Collins 2010-09-15 bug=619017
     # This test may be thrown off by the reference bug. To get around the
     # problem, flush and reset are called on the bug storm cache before
     # each call to the webservice. When lp's storm is updated to release
     # the committed fix for this bug, please see about updating this test.
     login(USER_EMAIL)
     bug = self.factory.makeBug()
     store = Store.of(bug)
     self.factory.makeBugComment(bug)
     self.factory.makeBugComment(bug)
     self.factory.makeBugComment(bug)
     webservice = LaunchpadWebServiceCaller('launchpad-library',
                                            'salgado-change-anything')
     collector = QueryCollector()
     collector.register()
     self.addCleanup(collector.unregister)
     url = '/bugs/%d/messages?ws.size=75' % bug.id
     # First request.
     store.flush()
     store.reset()
     response = webservice.get(url)
     self.assertThat(collector, HasQueryCount(LessThan(24)))
     with_2_count = collector.count
     self.failUnlessEqual(response.status, 200)
     login(USER_EMAIL)
     for i in range(50):
         self.factory.makeBugComment(bug)
     self.factory.makeBugAttachment(bug)
     logout()
     # Second request.
     store.flush()
     store.reset()
     response = webservice.get(url)
     self.assertThat(collector, HasQueryCount(Equals(with_2_count)))
Exemplo n.º 19
0
    def test_landing_targets_constant_queries(self):
        project = self.factory.makeProduct()
        with person_logged_in(project.owner):
            source = self.factory.makeBranch(target=project)
            source_url = api_url(source)
            webservice = webservice_for_person(
                project.owner, permission=OAuthPermission.WRITE_PRIVATE)

        def create_mp():
            with admin_logged_in():
                branch = self.factory.makeBranch(
                    target=project,
                    stacked_on=self.factory.makeBranch(
                        target=project,
                        information_type=InformationType.PRIVATESECURITY),
                    information_type=InformationType.PRIVATESECURITY)
                self.factory.makeBranchMergeProposal(source_branch=source,
                                                     target_branch=branch)

        def list_mps():
            webservice.get(source_url + '/landing_targets')

        list_mps()
        recorder1, recorder2 = record_two_runs(list_mps, create_mp, 2)
        self.assertThat(recorder1, HasQueryCount(LessThan(30)))
        self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
    def test_commits_after_each_item(self):
        # Test that the script commits after each item, not just at the end.
        uploads = [
            self.createWaitingAcceptancePackage(
                distroseries=self.factory.makeDistroSeries(
                    distribution=self.distro),
                sourcename='source%d' % i) for i in range(3)
        ]

        class UploadCheckingSynchronizer:

            commit_count = 0

            def beforeCompletion(inner_self, txn):
                pass

            def afterCompletion(inner_self, txn):
                if txn.status != 'Committed':
                    return
                inner_self.commit_count += 1
                done_count = len([
                    upload for upload in uploads
                    if upload.package_upload.status == PackageUploadStatus.DONE
                ])
                self.assertEqual(min(len(uploads), inner_self.commit_count),
                                 done_count)

        script = self.getScript([])
        switch_dbuser(self.dbuser)
        synch = UploadCheckingSynchronizer()
        transaction.manager.registerSynch(synch)
        script.main()
        self.assertThat(len(uploads), LessThan(synch.commit_count))
Exemplo n.º 21
0
 def assert_window_fullscreen(self, fullscreen):
     if fullscreen:
         self.assertThat(self.main_window.get_window().visibility,
                         Eventually(Equals(self.QWINDOW_FULLSCREEN)))
     else:
         self.assertThat(self.main_window.get_window().visibility,
                         Eventually(LessThan(self.QWINDOW_FULLSCREEN)))
Exemplo n.º 22
0
 def test_binary_query_counts(self):
     query_baseline = 40
     # Assess the baseline.
     collector = RequestTimelineCollector()
     collector.register()
     self.addCleanup(collector.unregister)
     ppa = self.factory.makeArchive()
     viewer = self.factory.makePerson()
     browser = self.getUserBrowser(user=viewer)
     with person_logged_in(viewer):
         # The baseline has one package, because otherwise the
         # short-circuit prevents the packages iteration happening at
         # all and we're not actually measuring scaling
         # appropriately.
         pkg = self.factory.makeBinaryPackagePublishingHistory(archive=ppa)
         url = canonical_url(ppa) + "/+packages"
     browser.open(url)
     self.assertThat(collector, HasQueryCount(LessThan(query_baseline)))
     expected_count = collector.count
     # Use all new objects - avoids caching issues invalidating the
     # gathered metrics.
     login(ADMIN_EMAIL)
     ppa = self.factory.makeArchive()
     viewer = self.factory.makePerson()
     browser = self.getUserBrowser(user=viewer)
     with person_logged_in(viewer):
         for i in range(3):
             pkg = self.factory.makeBinaryPackagePublishingHistory(
                 archive=ppa, distroarchseries=pkg.distroarchseries)
         url = canonical_url(ppa) + "/+packages"
     browser.open(url)
     self.assertThat(collector, HasQueryCount(Equals(expected_count)))
Exemplo n.º 23
0
def matches_time_or_just_before(time, tolerance=timedelta(seconds=10)):
    """
    Match a time to be equal to a certain time or just before it. Useful when
    checking for a time that is now +/- some amount of time.
    """
    return MatchesAll(GreaterThan(time - tolerance),
                      MatchesAny(LessThan(time), Equals(time)))
Exemplo n.º 24
0
    def test_autoscrolling_from_top(self):
        """Test the autoscrolling from the top of the Launcher"""
        self.open_apps_in_launcher()

        # Set the autoscroll_offset to 10 (this is arbitrary for this test).
        autoscroll_offset = 10

        launcher_instance = self.get_launcher()
        (x, y, w, h) = launcher_instance.geometry

        icons = self.unity.launcher.model.get_launcher_icons_for_monitor(
            self.launcher_monitor)
        num_icons = self.unity.launcher.model.num_launcher_icons()

        first_icon = icons[0]
        last_icon = icons[num_icons - 1]

        launcher_instance.move_mouse_over_launcher()

        # Move to the last icon in order to expand the top of the Launcher
        launcher_instance.move_mouse_to_icon(last_icon)

        # Make sure the first icon is off the screen or else there is no
        # scrolling.
        self.assertThat(first_icon.center.y, LessThan(y))

        # Autoscroll to the first icon
        launcher_instance.move_mouse_to_icon(first_icon, autoscroll_offset)

        (x_fin, y_fin) = self.mouse.position()

        # Make sure we ended up in the center of the first icon
        self.assertThat(x_fin, Equals(first_icon.center.x))
        self.assertThat(y_fin, Equals(first_icon.center.y))
Exemplo n.º 25
0
    def test_mocked_sleep_methods(self):
        with ElapsedTimeCounter() as time_counter:
            sleep.enable_mock()
            self.addCleanup(sleep.disable_mock)

            sleep(10)
            self.assertThat(time_counter.elapsed_time, LessThan(2))
Exemplo n.º 26
0
    def test_creating_proxy_for_segfaulted_app_fails_quicker(self):
        """Searching for a process that has died since launching, the search
        must fail before the 10 second timeout.

        """
        path = self.write_script(
            dedent("""\
            #!%s

            from time import sleep
            import sys

            sleep(1)
            sys.exit(1)
        """ % sys.executable))
        start = datetime.datetime.now()

        try:
            self.launch_test_application(path, app_type='qt')
        except ProcessSearchError:
            end = datetime.datetime.now()
        else:
            self.fail(
                "launch_test_application didn't raise expected exception")

        difference = end - start
        self.assertThat(difference.total_seconds(), LessThan(5))
 def test_date_created(self):
     result = self.factory.makeCodeImportResult()
     # date_created is "now", but there will have been a transaction
     # commit, so it won't be the same as UTC_NOW.
     self.assertThat(result.date_created,
                     LessThan(datetime.utcnow().replace(tzinfo=UTC)))
     self.assertEqual(result.date_created, result.date_job_finished)
Exemplo n.º 28
0
 def test_wait_select_single_succeeds_quickly(self):
     app = self.start_fully_featured_app()
     start_time = default_timer()
     main_window = app.wait_select_single('QMainWindow')
     end_time = default_timer()
     self.assertThat(main_window, NotEquals(None))
     self.assertThat(abs(end_time - start_time), LessThan(1))
Exemplo n.º 29
0
    def test_multiple_hud_reveal_does_not_break_launcher(self):
        """Multiple Hud reveals must not cause the launcher to set multiple
        apps as active.

        """
        launcher = self.unity.launcher.get_launcher_for_monitor(self.hud_monitor)

        # We need an app to switch to:
        self.process_manager.start_app('Character Map')
        # We need an application to play with - I'll use the calculator.
        self.process_manager.start_app('Calculator')
        sleep(1)

        # before we start, make sure there's zero or one active icon:
        num_active = self.get_num_active_launcher_icons()
        self.assertThat(num_active, LessThan(2), "Invalid number of launcher icons active before test has run!")

        # reveal and hide hud several times over:
        for i in range(3):
            self.unity.hud.ensure_visible()
            self.unity.hud.ensure_hidden()

        # click application icons for running apps in the launcher:
        icon = self.unity.launcher.model.get_icon(desktop_id="gucharmap.desktop")
        launcher.click_launcher_icon(icon)

        # see how many apps are marked as being active:
        num_active = self.get_num_active_launcher_icons()
        self.assertLessEqual(num_active, 1, "More than one launcher icon active after test has run!")
Exemplo n.º 30
0
    def test__returns_at_most_60kiB_of_JSON(self):
        # Configure the rack controller subnet to be very large so it
        # can hold that many BMC connected to the interface for the rack
        # controller.
        rack = factory.make_RackController(power_type='')
        rack_interface = rack.get_boot_interface()
        subnet = factory.make_Subnet(
            cidr=str(factory.make_ipv6_network(slash=8)))
        factory.make_StaticIPAddress(ip=factory.pick_ip_in_Subnet(subnet),
                                     subnet=subnet,
                                     interface=rack_interface)

        # Ensure that there are at least 64kiB of power parameters (when
        # converted to JSON) in the database.
        example_parameters = {"key%d" % i: "value%d" % i for i in range(250)}
        remaining = 2**16
        while remaining > 0:
            node = self.make_Node(bmc_connected_to=rack,
                                  power_parameters=example_parameters)
            remaining -= len(json.dumps(node.get_effective_power_parameters()))

        nodes = list_cluster_nodes_power_parameters(
            rack.system_id, limit=None)  # Remove numeric limit.

        # The total size of the JSON is less than 60kiB, but only a bit.
        nodes_json = map(json.dumps, nodes)
        nodes_json_lengths = map(len, nodes_json)
        nodes_json_length = sum(nodes_json_lengths)
        expected_maximum = 60 * (2**10)  # 60kiB
        self.expectThat(nodes_json_length, LessThan(expected_maximum + 1))
        expected_minimum = 50 * (2**10)  # 50kiB
        self.expectThat(nodes_json_length, GreaterThan(expected_minimum - 1))