From e20cfb534e276fa7762980bbdd6d633a7ce99ccc Mon Sep 17 00:00:00 2001 From: mike Date: Thu, 19 Mar 2009 20:06:38 +0000 Subject: Merge branch 'tutorial_toolkit' of ssh://mike@bobthebuilder.mine.nu:8080/home/git into tutorial_toolkit Conflicts: source/external/source/sugar-toolkit/src/sugar/tutorius/core.py --- (limited to 'src/sugar/tutorius/tests') diff --git a/src/sugar/tutorius/tests/coretests.py b/src/sugar/tutorius/tests/coretests.py new file mode 100644 index 0000000..7792930 --- /dev/null +++ b/src/sugar/tutorius/tests/coretests.py @@ -0,0 +1,212 @@ +# Copyright (C) 2009, Tutorius.org +# Copyright (C) 2009, Michael Janelle-Montcalm +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" +Core Tests + +This module contains all the tests that pertain to the usage of the Tutorius +Core. This means that the Event Filters, the Finite State Machine and all the +related elements and interfaces are tested here. + +""" + +import unittest + +import logging +from sugar.tutorius.actions import Action, OnceWrapper +from sugar.tutorius.core import * +from sugar.tutorius.filters import * + +# Helper classes to help testing +class SimpleTutorial(Tutorial): + """ + Fake tutorial + """ + def __init__(self, start_name="INIT"): + #Tutorial.__init__(self, "Simple Tutorial", None) + self.current_state_name = start_name + self.activity = "TODO : This should be an activity" + + def set_state(self, name): + self.current_state_name = name + +class TrueWhileActiveAction(Action): + """ + This action's active member is set to True after a do and to False after + an undo. + + Used to verify that a State correctly triggers the do and undo actions. + """ + def __init__(self): + self.active = False + + def do(self): + self.active = True + + def undo(self): + self.active = False + + +class CountAction(Action): + """ + This action counts how many times it's do and undo methods get called + """ + def __init__(self): + self.do_count = 0 + self.undo_count = 0 + + def do(self): + self.do_count += 1 + + def undo(self): + self.undo_count += 1 + +class TriggerEventFilter(EventFilter): + """ + This event filter can be triggered by simply calling its execute function. + + Used to fake events and see the effect on the FSM. + """ + def __init__(self, next_state): + EventFilter.__init__(self, next_state) + self.toggle_on_callback = False + + def install_handlers(self, callback, **kwargs): + """ + Forsakes the incoming callback function and just set the inner one. + """ + self._callback = self._inner_cb + + def _inner_cb(self, event_filter): + self.toggle_on_callback = not self.toggle_on_callback + +class BaseActionTests(unittest.TestCase): + def test_do_unimplemented(self): + act = Action() + try: + act.do() + assert False, "do() should trigger a NotImplemented" + except NotImplementedError: + assert True, "do() should trigger a NotImplemented" + + def test_undo(self): + act = Action() + act.undo() + assert True, "undo() should never fail on the base action" + + +class OnceWrapperTests(unittest.TestCase): + def test_onceaction_toggle(self): + """ + Validate that the OnceWrapper wrapper works properly using the + CountAction + """ + act = CountAction() + wrap = OnceWrapper(act) + + assert act.do_count == 0, "do() should not have been called in __init__()" + assert act.undo_count == 0, "undo() should not have been called in __init__()" + + wrap.undo() + + assert act.undo_count == 0, "undo() should not be called if do() has not been called" + + wrap.do() + assert act.do_count == 1, "do() should have been called once" + + wrap.do() + assert act.do_count == 1, "do() should have been called only once" + + wrap.undo() + assert act.undo_count == 1, "undo() should have been called once" + + wrap.undo() + assert act.undo_count == 1, "undo() should have been called only once" + + +# State testing class +class StateTest(unittest.TestCase): + """ + This class has to test the State interface as well as the expected + functionality. + """ + + def test_action_toggle(self): + """ + Validate that the actions are properly done on setup and undone on + teardown. + + Pretty awesome. + """ + act = TrueWhileActiveAction() + + state = State("action_test", action_list=[act]) + + assert act.active == False, "Action is not initialized properly" + + state.setup() + + assert act.active == True, "Action was not triggered properly" + + state.teardown() + + assert act.active == False, "Action was not undone properly" + + def test_event_filter(self): + """ + Tests the fact that the event filters are correctly installed on setup + and uninstalled on teardown. + """ + event_filter = TriggerEventFilter("second_state") + + state = State("event_test", event_filter_list=[event_filter]) + state.set_tutorial(SimpleTutorial()) + + assert event_filter.toggle_on_callback == False, "Wrong init of event_filter" + assert event_filter._callback == None, "Event filter has a registered callback before installing handlers" + + state.setup() + + assert event_filter._callback != None, "Event filter did not register callback!" + + # 'Trigger' the event - This is more like a EventFilter test. + event_filter.do_callback() + + assert event_filter.toggle_on_callback == True, "Event filter did not execute callback" + + state.teardown() + + assert event_filter._callback == None, "Event filter did not remove callback properly" + + def test_warning_set_tutorial_twice(self): + """ + Calls set_tutorial twice and expects a warning on the second. + """ + state = State("start_state") + tut = SimpleTutorial("First") + tut2 = SimpleTutorial("Second") + + state.set_tutorial(tut) + + try: + state.set_tutorial(tut2) + assert False, "No RuntimeWarning was raised on second set_tutorial" + except : + pass + + +if __name__ == "__main__": + unittest.main() diff --git a/src/sugar/tutorius/tests/overlaytests.py b/src/sugar/tutorius/tests/overlaytests.py new file mode 100644 index 0000000..b5fd209 --- /dev/null +++ b/src/sugar/tutorius/tests/overlaytests.py @@ -0,0 +1,115 @@ +# Copyright (C) 2009, Tutorius.org +# Copyright (C) 2009, Simon Poirier +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" +GUI Tests + +This module contains all the tests that pertain to the usage of the Tutorius +overlay mechanism used to display objects on top of the application. +""" + +import unittest + +import logging +import gtk, gobject +from sugar.tutorius.actions import Action, BubbleMessage +import sugar.tutorius.overlayer as overlayer + +class CanvasDrawable(object): + def __init__(self): + self._no_expose = False + self.exposition_count = 0 + def _set_no_expose(self, value): + self._no_expose = value + def draw_with_context(self, context): + self.exposition_count += 1 + no_expose = property(fset=_set_no_expose) + + +class OverlayerTest(unittest.TestCase): + def test_cairodrawable_iface(self): + """ + Quickly validates that all our cairo widgets have a minimal interface + implemented. + """ + drawables = [overlayer.TextBubble] + for widget in drawables: + for attr in filter(lambda s:s[0]!='_', dir(CanvasDrawable)): + assert hasattr(widget, attr), \ + "%s not implementing CanvasDrawable iface"%widget.__name__ + + + def test_drawn(self): + """ + Ensures a cairo widget draw method is called at least once in + a real gui app. + """ + win = gtk.Window(type=gtk.WINDOW_TOPLEVEL) + + btn = gtk.Button() + btn.show() + overlay = overlayer.Overlayer(btn) + win.add(overlay) + # let's also try to draw substitute button label + lbl = overlayer.TextBubble("test!") + assert lbl.label == 'test!', \ + "label property mismatch" + btn.show() + lbl.show() + btn.add(lbl) + + lbl.no_expose = True + assert lbl.no_expose, "wrong no_expose evaluation" + lbl.no_expose = False + assert not lbl.no_expose, "wrong no_expose evaluation" + + + widget = overlayer.TextBubble("testing msg!", tailpos=(10,-20)) + widget.exposition_count = 0 + # override draw method + def counter(ctx, self=widget): + self.exposition_count += 1 + self.real_exposer(ctx) + widget.real_exposer = widget.draw_with_context + widget.draw_with_context = counter + # centering allows to test the blending with the label + overlay.put(widget, 50, 50) + widget.show() + assert widget.no_expose, \ + "Overlay should overide exposition handling of widget" + assert not lbl.no_expose, \ + "Non-overlayed cairo should expose as usual" + + # force widget realization + # the child is flagged to be redrawn, the overlay should redraw too. + win.set_default_size(100, 100) + win.show() + + while gtk.events_pending(): + gtk.main_iteration(block=False) + # visual validation: there should be 2 visible bubbles, one as label, + # one as overlay + import time + time.sleep(1) + # as x11 events are asynchronous, wait a bit before assuming it went + # wrong. + while gtk.events_pending(): + gtk.main_iteration(block=False) + assert widget.exposition_count>0, "overlay widget should expose" + + +if __name__ == "__main__": + unittest.main() diff --git a/src/sugar/tutorius/tests/run-tests.py b/src/sugar/tutorius/tests/run-tests.py new file mode 100755 index 0000000..db10c54 --- /dev/null +++ b/src/sugar/tutorius/tests/run-tests.py @@ -0,0 +1,44 @@ +#!/usr/bin/python +# This is a dumb script to run tests on the sugar-jhbuild installed files +# The path added is the default path for the jhbuild build + +INSTALL_PATH="../../../../../../install/lib/python2.5/site-packages/" + +import os, sys +sys.path.insert(0, + os.path.abspath(INSTALL_PATH) +) + +FULL_PATH = os.path.join(INSTALL_PATH,"sugar/tutorius") +GLOB_PATH = os.path.join(FULL_PATH,"*.py") +import unittest +from glob import glob + +import sys +if __name__=='__main__': + if "--coverage" in sys.argv: + sys.argv=[arg for arg in sys.argv if arg != "--coverage"] + import coverage + coverage.erase() + #coverage.exclude('raise NotImplementedError') + coverage.start() + + import coretests + import servicestests + + + suite = unittest.TestSuite() + suite.addTests(unittest.findTestCases(coretests)) + suite.addTests(unittest.findTestCases(servicestests)) + + runner = unittest.TextTestRunner() + runner.run(suite) + + coverage.stop() + coverage.report(glob(GLOB_PATH)) + coverage.erase() + else: + from coretests import * + from servicestests import * + + unittest.main() -- cgit v0.9.1