예제 #1
0
def test_automatic_starting_is_threadsafe(web_fixture, wsgi_fixture):
    """If multiple concurrent threads handle the first requests, the application is only started once."""
    wsgi_app = ReahlWSGIApplicationSlowStartingStub(
        wsgi_fixture.config, start_on_first_request=True)

    wsgi_app.block_start(
    )  #start will be calling this - use it to delay the first start

    assert not wsgi_app.started

    with CallMonitor(wsgi_app.start) as monitor:

        # simulate multiple requests hitting an app, whoever is first, will cause it to start
        thread1 = threading.Thread(target=wsgi_fixture.call_wsgi_app,
                                   args=(wsgi_app, ))
        thread1.start()

        thread2 = threading.Thread(target=wsgi_fixture.call_wsgi_app,
                                   args=(wsgi_app, ))
        thread2.start()

        wsgi_app.unblock_start()
        thread1.join()
        thread2.join()

    assert wsgi_app.started
    assert len(monitor.calls) == 1
예제 #2
0
def test_schedule_executes_in_order():
    """A MigrationSchedule is used internally to schedule calls in different phases. The calls 
       scheduled in each phase are executed in the order the phases have been set up on the MigrationSchedule.
       Within a phase, the calls are executed in the order they were registered in that phase.
    """

    schedule_names = ['a', 'b', 'c']
    migration_schedule = MigrationSchedule(*schedule_names)

    class SomeObject(object):
        def do_something(self, arg):
            pass

    some_object = SomeObject()

    # schedule calls not in registered order
    with CallMonitor(some_object.do_something) as monitor:
        migration_schedule.schedule('c', some_object.do_something, 'c1')
        migration_schedule.schedule('a', some_object.do_something, 'a1')
        migration_schedule.schedule('b', some_object.do_something, 'b')
        migration_schedule.schedule('a', some_object.do_something, 'a2')
        migration_schedule.schedule('c', some_object.do_something, 'c2')

    migration_schedule.execute_all()

    actual_order = [call.args[0] for call in monitor.calls]
    expected_order = ['a1', 'a2', 'b', 'c1', 'c2']
    assert actual_order == expected_order
예제 #3
0
def test_schedule_executes_in_order():
    """A MigrationSchedule is used internally to schedule calls in different phases. The calls 
       scheduled in each phase are executed in the order the phases have been set up on the MigrationSchedule.
       Within a phase, the calls are executed in the order they were registered in that phase.
    """
    
    migration_schedule = MigrationSchedule(EmptyStub(), EmptyStub(), [])

    class SomeObject:
        def do_something(self, arg):
            pass
    some_object = SomeObject()

    # schedule calls not in registered order
    with CallMonitor(some_object.do_something) as monitor:
        migration_schedule.schedule('cleanup', EmptyStub(), EmptyStub(), some_object.do_something, '1')
        migration_schedule.schedule('create_fk', EmptyStub(), EmptyStub(), some_object.do_something, '2')
        migration_schedule.schedule('data', EmptyStub(), EmptyStub(), some_object.do_something, '3')
        migration_schedule.schedule('indexes', EmptyStub(), EmptyStub(), some_object.do_something, '4')
        migration_schedule.schedule('create_pk', EmptyStub(), EmptyStub(), some_object.do_something, '5')

        migration_schedule.schedule('alter', EmptyStub(), EmptyStub(), some_object.do_something, 'c1')
        migration_schedule.schedule('drop_pk', EmptyStub(), EmptyStub(),some_object.do_something, 'a1')
        migration_schedule.schedule('pre_alter', EmptyStub(), EmptyStub(),some_object.do_something, 'b')
        migration_schedule.schedule('drop_pk', EmptyStub(), EmptyStub(),some_object.do_something, 'a2')
        migration_schedule.schedule('alter', EmptyStub(), EmptyStub(),some_object.do_something, 'c2')

    migration_schedule.execute_all()

    actual_order = [call.args[0] for call in monitor.calls]
    expected_order = ['a1', 'a2', 'b', 'c1', 'c2', '5', '4', '3', '2', '1']
    assert actual_order == expected_order
예제 #4
0
def test_monitor():
    """A CallMonitor records each call to a method, and its arguments."""

    class SomethingElse:
        def foo(self, n, y=None):
            self.n = n
            return y

    s = SomethingElse()
    original_method = s.foo.__func__

    with CallMonitor(s.foo) as monitor:
        assert s.foo(1, y='a') == 'a'
        assert s.foo(2) is None

    assert s.foo.__func__ is original_method
    assert s.n == 2
    assert monitor.times_called == 2
    assert monitor.calls[0].args == (1,)
    assert monitor.calls[0].kwargs == {'y':'a'}
    assert monitor.calls[0].return_value == 'a'

    assert monitor.calls[1].args == (2,)
    assert monitor.calls[1].kwargs == {}
    assert monitor.calls[1].return_value is None
예제 #5
0
def exception_handling(fixture):
    """When a DomainException happens during the handling of an Event:

       The database is rolled back.
       The browser is redirected to GET the original view again (not the target).
       The screen still displays the values the user initially typed, not those on the ModelObject.
    """
    class ModelObject(object):
        def handle_event(self):
            self.field_name = 1
            raise DomainException()

        @exposed
        def events(self, events):
            events.an_event = Event(label='click me',
                                    action=Action(self.handle_event))

        @exposed
        def fields(self, fields):
            fields.field_name = IntegerField(default=3)

    model_object = ModelObject()

    class MyForm(Form):
        def __init__(self, view, name, other_view):
            super(MyForm, self).__init__(view, name)
            self.define_event_handler(model_object.events.an_event,
                                      target=other_view)
            self.add_child(ButtonInput(self, model_object.events.an_event))
            self.add_child(TextInput(self, model_object.fields.field_name))

    class MainUI(UserInterface):
        def assemble(self):
            self.define_page(HTML5Page).use_layout(
                PageLayout(contents_layout=ColumnLayout('main').with_slots()))
            home = self.define_view('/', title='Home page')
            other_view = self.define_view('/page2', title='Page 2')
            home.set_slot('main', MyForm.factory('myform', other_view))

    wsgi_app = fixture.new_wsgi_app(site_root=MainUI)
    fixture.reahl_server.set_app(wsgi_app)

    fixture.driver_browser.open('/')

    vassert(not hasattr(model_object, 'field_name'))
    fixture.driver_browser.type("//input[@type='text']", '5')

    # any database stuff that happened when the form was submitted was rolled back
    with CallMonitor(fixture.system_control.orm_control.rollback) as monitor:
        fixture.driver_browser.click("//input[@value='click me']")
    vassert(monitor.times_called == 1)

    # the value input by the user is still displayed on the form, NOT the actual value on the model object
    vassert(model_object.field_name == 1)
    retained_value = fixture.driver_browser.get_value("//input[@type='text']")
    vassert(retained_value == '5')

    # the browser is still on the page with the form which triggered the exception
    vassert(fixture.driver_browser.current_url.path == '/')
예제 #6
0
def test_slave_process_terminates_then_waits():
    """When the SlaveProcess is terminated, it waits for the OS process to die before returning."""

    slave_process = SlaveProcessStub()
    slave_process.start()

    with CallMonitor(slave_process.process.wait) as wait_monitor:
        slave_process.terminate()
    assert wait_monitor.times_called == 1
예제 #7
0
def test_handling_uncaught_exceptions(web_fixture):
    """If an uncaught exception is raised, the session is closed properly."""
    @stubclass(ReahlWSGIApplication)
    class ReahlWSGIApplicationStub2(ReahlWSGIApplicationStub):
        def resource_for(self, request):
            raise AssertionError('this an unknown breakage')

    app = ReahlWSGIApplicationStub2(web_fixture.config)
    browser = Browser(app)

    with CallMonitor(app.system_control.finalise_session) as monitor:
        assert monitor.times_called == 0
        with expected(AssertionError):
            browser.open('/')
        assert monitor.times_called == 1
예제 #8
0
    def regenerating_method_results(self, fixture):
        """If a MethodResult is set up to replay_request=True, the view it is part of (and thus itself) is recreated
           before the (new incarnation of the) MethodResult generates its actual response. Replaying the request means recreating
           all Widgets on the current View as well as the MethodResult itself. The construction of any of these 
           objects may happen differently because of the changes made during the RemoteMethod's execution. Replaying
           the request ensures that the MethodResult reflects such changes, yet ensures that the RemoteMethod
           is not executed twice.
        """

        wsgi_app = fixture.new_wsgi_app(remote_method=fixture.remote_method)
        browser = Browser(wsgi_app)

        with CallMonitor(fixture.system_control.orm_control.commit) as monitor:
            browser.post('/_amethod_method', {})
        vassert(browser.raw_html == fixture.expected_response)
        vassert(monitor.times_called == 2)
예제 #9
0
    def test_monitor_class_methods(self):
        """A CallMonitor works for class methods as well."""
        class SomethingElse(object):
            @classmethod
            def foo(cls):
                return cls

        original_method = SomethingElse.foo

        with CallMonitor(SomethingElse.foo) as monitor:
            cls = SomethingElse.foo()
            assert cls is SomethingElse

        assert SomethingElse.foo == original_method
        assert monitor.times_called == 1
        assert monitor.calls[0].args == ()
        assert monitor.calls[0].return_value == SomethingElse
예제 #10
0
def test_schedule_executes_phases_with_parameters():
    """When a MigrationSchedule executes the calls that were scheduled from a Migration, 
       the methods are actually called, and passed the correct arguments."""

    class SomeObject:
        def please_call_me(self, arg, kwarg=None):
            pass
    some_object = SomeObject()
    
    migration_schedule = MigrationSchedule(EmptyStub(), EmptyStub(), [])
    migration = Migration(migration_schedule)

    with CallMonitor(some_object.please_call_me) as monitor:
        migration.schedule('alter', some_object.please_call_me, 'myarg', kwarg='mykwarg')

    migration_schedule.execute_all()

    assert monitor.calls[0].args == ('myarg',)
    assert monitor.calls[0].kwargs == dict(kwarg='mykwarg')
예제 #11
0
    def config_defaults(self, fixture):
        """Defaults that are dangerous to leave at their at their settings can be marked as such. 
           This will result in a logged warning."""

        fixture.set_config_spec(
            easter_egg,
            'reahl.component_dev.test_config:ConfigWithDangerousDefaultedSetting'
        )

        # Usually this happens inside other infrastructure, such as the implementation of reahl serve (see reahl-dev)
        config = StoredConfiguration(fixture.config_dir.name)
        with CallMonitor(logging.getLogger(
                'reahl.component.config').warning) as monitor:
            config.configure()

        # A warning was issued with warn severity to the log
        logged_message = monitor.calls[0].args[0]
        message_regex = 'some_key.some_setting in /.*/config_file_for_this_egg.py is using a dangerous default setting'
        vassert(re.match(message_regex, logged_message))

        # The default value is still used
        vassert(config.some_key.some_setting == 'default value')
예제 #12
0
def test_config_defaults_dangerous(config_with_files):
    """Defaults that are dangerous to leave at their at their settings can be marked as such. 
       This will result in a logged warning."""

    fixture = config_with_files
    fixture.set_config_spec(
        easter_egg,
        'reahl.component_dev.test_config:ConfigWithDangerousDefaultedSetting')

    # Usually this happens inside other infrastructure, such as the implementation of reahl serve (see reahl-dev)
    config = StoredConfiguration(fixture.config_dir.name)
    with CallMonitor(
            logging.getLogger('reahl.component.config').warning) as monitor:
        config.configure()

    # A warning was issued with warn severity to the log
    logged_message = monitor.calls[0].args[0]
    message_regex = '^some_key.some_setting has been defaulted to a value not suitable for production use: "default value". You can set it in /.*/config_file_for_this_egg.py'
    assert re.match(message_regex, logged_message)

    # The default value is still used
    assert config.some_key.some_setting == 'default value'
예제 #13
0
def test_regenerating_method_results(reahl_system_fixture, web_fixture,
                                     remote_method_fixture,
                                     regenerate_method_result_scenarios):
    """If a MethodResult is set up to replay_request=True, the view it is part of (and thus itself) is recreated
       before the (new incarnation of the) MethodResult generates its actual response. Replaying the request means recreating
       all Widgets on the current View as well as the MethodResult itself. The construction of any of these
       objects may happen differently because of the changes made during the RemoteMethod's execution. Replaying
       the request ensures that the MethodResult reflects such changes, yet ensures that the RemoteMethod
       is not executed twice.
    """

    wsgi_app = remote_method_fixture.new_wsgi_app(
        remote_method=regenerate_method_result_scenarios.remote_method)
    browser = Browser(wsgi_app)

    import sqlalchemy.orm

    @stubclass(sqlalchemy.orm.Session)
    class TransactionStub:
        is_active = True

        def commit(self):
            pass

        def rollback(self):
            pass

    def wrapped_nested_transaction():
        return web_fixture.nested_transaction

    web_fixture.nested_transaction = TransactionStub()
    with replaced(Session().begin_nested, wrapped_nested_transaction):
        with CallMonitor(web_fixture.nested_transaction.commit) as monitor:
            browser.post('/_amethod_method', {})
        assert browser.raw_html == regenerate_method_result_scenarios.expected_response
        assert monitor.times_called == 2
예제 #14
0
def test_web_session_handling(reahl_system_fixture, web_fixture):
    """The core web framework (this egg) does not implement a notion of session directly.
       It relies on such a notion, but expects an implementation for this to be supplied.

       An implementation should implement the UserSessionProtocol. The implementation
       is thus responsible for associating an instance of itself with the current request.

       The implementation to be used is set in the web.session_class configuration setting.

       The framework ensures that an instance of web.session_class is available during any
       Request. The database is committed before any user code executes, so that any database activity
       done by web.session_class would be committed even if an exception in the user code occurs.
       After user code is executed, methods are called on the web.session_class so that it can set
       its key on the response and save the last_activity_time.
       Finally, another commit is issued to the database so that any database activity during these last
       actions would also be saved.
    """
    stubclass(UserSessionProtocol)

    class UserSessionStub(UserSessionProtocol):
        session = None
        last_activity_time_set = False
        key_is_set = False

        @classmethod
        def for_current_session(cls):
            assert None, 'Not implemented'

        @classmethod
        def get_or_create_session(cls):
            cls.session = cls()
            return cls.session

        @classmethod
        def initialise_web_session_on(cls, context):
            context.session = cls.get_or_create_session()

        def set_session_key(self, response):
            self.key_is_set = True
            self.saved_response = response

        def is_active(self):
            pass

        def is_secured(self):
            pass

        def set_as_logged_in(self, party, stay_logged_in):
            pass

        def log_out(self):
            pass

        def set_last_activity_time(self):
            self.last_activity_time_set = True

        def get_interface_locale(self):
            return 'en_gb'

    import sqlalchemy.orm

    @stubclass(sqlalchemy.orm.Session)
    class TransactionStub:
        is_active = True

        def commit(self):
            pass

        def rollback(self):
            pass

    web_fixture.nested_transaction = TransactionStub()

    def wrapped_nested_transaction():
        return web_fixture.nested_transaction

    with replaced(Session().begin_nested, wrapped_nested_transaction):
        # Setting the implementation in config
        web_fixture.config.web.session_class = UserSessionStub
        with CallMonitor(web_fixture.nested_transaction.commit) as monitor:

            @stubclass(Resource)
            class ResourceStub:
                should_commit = True

                def cleanup_after_transaction(self):
                    assert monitor.times_called == 2  # The database has been committed after user code started executed, before cleanup

                def handle_request(self, request):
                    context = ExecutionContext.get_context()
                    assert context.session is UserSessionStub.session  # By the time user code executes, the session is set
                    assert monitor.times_called == 1  # The database has been committed before user code started executing
                    assert context.session.last_activity_time_set
                    assert not UserSessionStub.session.key_is_set
                    return Response()

            @stubclass(ReahlWSGIApplication)
            class ReahlWSGIApplicationStub2(ReahlWSGIApplicationStub):
                def resource_for(self, request):
                    return ResourceStub()

            browser = Browser(ReahlWSGIApplicationStub2(web_fixture.config))

            # A session is obtained, and the correct params passed to the hook methods
            assert not UserSessionStub.session  # Before the request, the session is not yet set
            assert monitor.times_called == 0  # ... and the database is not yet committed
            browser.open('/')

            assert monitor.times_called == 2  # The database is committed to save session changes before user code and again after user code executed
            assert UserSessionStub.session  # The session was set
            assert UserSessionStub.session.key_is_set  # The set_session_key was called
            assert UserSessionStub.session.saved_response.status_int is 200  # The correct response was passed to set_session_key

            assert UserSessionStub.session.last_activity_time_set  # set_last_activity_time was called
예제 #15
0
    def web_session_handling(self, fixture):
        """The core web framework (this egg) does not implement a notion of session directly.
           It relies on such a notion, but expects an implementation for this to be supplied.

           An implementation should implement the UserSessionProtocol. The implementation
           is thus responsible for associating an instance of itself with the current request.

           The implementation to be used is set in the web.session_class configuration setting.

           The framework ensures that an instance of web.session_class is available during any
           Request. The database is committed before any user code executes, so that any database activity
           done by web.session_class would be committed even if an exception in the user code occurs.
           After user code is executed, methods are called on the web.session_class so that it can set 
           its key on the response and save the last_activity_time.
           Finally, another commit is issued to the database so that any database activity during these last
           actions would also be saved.
        """
        stubclass(UserSessionProtocol)
        class UserSessionStub(UserSessionProtocol):
            session = None
            last_activity_time_set = False
            key_is_set = False
            @classmethod
            def for_current_session(cls):
                assert None, 'Not implemented'

            @classmethod
            def get_or_create_session(cls):
                cls.session = cls()
                return cls.session

            def set_session_key(self, response):
                self.key_is_set = True
                self.saved_response = response

            def is_active(self): pass
            def is_secured(self): pass
            def set_as_logged_in(self, party, stay_logged_in): pass
            def log_out(self): pass

            def set_last_activity_time(self): 
                self.last_activity_time_set = True

            def get_interface_locale(self):
                return 'en_gb'

        # Setting the implementation in config
        fixture.config.web.session_class = UserSessionStub

        with CallMonitor(fixture.system_control.orm_control.commit) as monitor:
            @stubclass(Resource)
            class ResourceStub(object):
                def handle_request(self, request):
                    context = WebExecutionContext.get_context()
                    vassert( context.session is UserSessionStub.session )  # By the time user code executes, the session is set
                    vassert( monitor.times_called == 1 )                      # The database has been committed
                    vassert( not context.session.last_activity_time_set )     
                    vassert( not UserSessionStub.session.key_is_set )
                    return Response()
        
            @stubclass(ReahlWSGIApplication)
            class ReahlWSGIApplicationStub2(ReahlWSGIApplicationStub):
                def resource_for(self, request):
                    return ResourceStub()

            browser = Browser(ReahlWSGIApplicationStub2(fixture.config))

            # A session is obtained, and the correct params passed to the hook methods
            vassert( not UserSessionStub.session )      # Before the request, the session is not yet set
            vassert( monitor.times_called == 0 )           # ... and the database is not yet committed
            browser.open('/')
            
            vassert( monitor.times_called == 1 )           # The database is committed after user code executed
            vassert( UserSessionStub.session )          # The session was set
            vassert( UserSessionStub.session.key_is_set ) # The set_session_key was called
            vassert( UserSessionStub.session.saved_response.status_int is 200 ) # The correct response was passed to set_session_key

            vassert( UserSessionStub.session.last_activity_time_set ) # set_last_activity_time was called