# Solfege - free ear training software
# vim: set fileencoding=utf-8 :
# Copyright (C) 2007, 2008 Tom Cato Amundsen
# License is GPL, see file COPYING

from __future__ import absolute_import
import gtk
import unittest
import time

from solfege.testlib import I18nSetup
import solfege.lessonfile
solfege.lessonfile._test_mode = True
from solfege.lessonfile import *
from solfege import dataparser
from solfege import mpd
from solfege.mpd import mpdutils
from solfege import cfg

class TestRhythmStaff(unittest.TestCase):
    def test_init(self):
        rs = RhythmStaff()
        rs.append(Bar(Rat(4, 4)))
    def test_cmp(self):
        r1 = RhythmStaff()
        r1.append(Bar(Rat(4, 4)))
        r2 = RhythmStaff()
        r2.append(Bar(Rat(4, 4)))
        self.assertEquals(r1, r2)
        r2.append(Bar(Rat(4, 4)))
        self.assertNotEquals(r1, r2)
        r1.append(Bar(Rat(4, 4)))
        self.assertEquals(r1, r2)
        r1[-1].add_note(u"4")
        self.assertNotEquals(r1, r2)
        r2[-1].add_note(u"4")
        self.assertNotEquals(r1, r2)

class TestBar(unittest.TestCase):
    def test1(self):
        b = Bar(Rat(4, 4))
        b.add_note(u"4")
        b.add_note(u"4.")
        b.add_rest(u"8")
        b.add_note(u"4")
        self.assertRaises(Bar.FullException, lambda: b.add_note(u"4"))
    def test_err_1(self):
        b = Bar(Rat(4, 4))
        self.assertRaises(Bar.BadDuration, lambda: b.add_note("x"))
        self.assertRaises(Bar.BadDuration, lambda: b.add_note("4,"))
        self.assertRaises(Bar.BadDuration, lambda: b.add_note("."))
        self.assertRaises(Bar.BadDuration, lambda: b.add_note("4.'"))
        self.assertRaises(Bar.BadDuration, lambda: b.add_note(4))


class TestParser(unittest.TestCase):
    def test_bad_musictype(self):
        s = 'question {\n' \
            'music = music("c e g", "chor")\n' \
            '}'
        p = LessonfileCommon()
        try:
            p.parse_string(s)
        except dataparser.WrongArgumentCount, e:
            self.assertEquals('(line 1): question {\n'
                              '(line 2): music = music("c e g", "chor")\n'
                              '                  ^',
                              e.m_nonwrapped_text)
    def test_gettext(self):
        s = 'question {\n' \
            '  name = _("chord|m7")\n' \
            '  iname = _i("chord|m7")\n' \
            '}'
        p = LessonfileCommon()
        p.parse_string(s)
        self.assertEquals(p.m_questions[0].name, "chord|m7")
        self.assertEquals(p.m_questions[0].name.cval, "chord|m7")
        self.assertEquals(p.m_questions[0].iname, "m7")
        self.assertEquals(p.m_questions[0].iname.cval, "chord|m7")

class LessonfileTestCase(unittest.TestCase):
    def _m(self, idx=0):
        return self.p.m_questions[idx]['music']

class TestMpdTransposable(unittest.TestCase):
    def setUp(self):
        cfg.set_bool('config/override_default_instrument', False)
        self.p = QuestionsLessonfile()
        self.p.parse_string("""
        header { random_transpose = "atonal", -5, 5 }
        question { music = chord("c e g") }
        """)
        self.p.m_transpose = mpd.MusicalPitch.new_from_notename("d'")
        self.p._idx = 0
    def test_get_musicdata_transposed(self):
        self.assertEquals("d fis a", self.p.m_questions[0]['music'].get_musicdata_transposed(self.p))
        self.p.header.random_transpose = ("key", -5, 6)
        # get_musicdata_transposed works for all transposition modes.
        self.assertEquals("d fis a", self.p.m_questions[0]['music'].get_musicdata_transposed(self.p))
        self.assertEquals("c e g", self.p.m_questions[0]['music'].m_musicdata)

class TestChord(unittest.TestCase):
    def setUp(self):
        cfg.set_bool('config/override_default_instrument', False)
        self.p = QuestionsLessonfile()
        self.p.parse_string("""
        header { random_transpose = no }
        question { music = chord("c e g") }
        """)
        self.p.m_transpose = mpd.MusicalPitch.new_from_notename("d'")
        self.p._idx = 0
    def get_get_mpd_music_string(self):
        self.assertEquals(self.p.m_questions[0]['music'].get_mpd_music_string(self.p),
            r"\staff{ <c e g> }")
        self.p.header.random_transpose = (True,)
        self.assertEquals(self.p.m_questions[0]['music'].get_mpd_music_string(self.p),
            r"\staff\transpose d'{ <c e g> }")
    def test_get_music_as_notename_list(self):
        self.assertEquals(self.p.m_questions[0]['music'].get_music_as_notename_list(self.p), ['c', 'e', 'g'])
        self.p.header.random_transpose = (True,)
        self.p.m_transpose = mpd.MusicalPitch.new_from_notename("d'")
        self.assertEquals(self.p.m_questions[0]['music'].get_music_as_notename_list(self.p), ['d', 'fis', 'a'])
    def test_get_lilypond_code(self):
        self.assertEquals(self.p.m_questions[0]['music'].get_lilypond_code(self.p), "\\score{  \\transpose c' d'{ <c e g> } \\layout {   ragged-last = ##t   \\context { \\Staff \\remove \"Time_signature_engraver\" }  }}")
        self.p.header.random_transpose = ('atonal', -5, 5)
        self.assertEquals(self.p.m_questions[0]['music'].get_lilypond_code(self.p), "\\score{    { <d fis a> } \\layout {   ragged-last = ##t   \\context { \\Staff \\remove \"Time_signature_engraver\" }  }}")
    def test_get_lilypond_code_first_note(self):
        self.assertEquals(self.p.m_questions[0]['music'].get_lilypond_code_first_note(self.p), r"\transpose c' d'{ c }")
        self.p.header.random_transpose = ('atonal', -5, 5)
        self.assertEquals(self.p.m_questions[0]['music'].get_lilypond_code_first_note(self.p), r"{ d }")
    def test_play(self):
        question = self.p.m_questions[0]
        question['music'].play(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(),
            "t60/4 p0:0 v0:100 n48 n52 n55 d1/4 o48 o52 o55")
    def test_3patch_play(self):
        cfg.set_bool('config/override_default_instrument', True)
        question = self.p.m_questions[0]
        question['music'].play(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(1),
            "t60/4 p0:1 v0:121 n0:48 p1:2 v1:122 n1:52 p2:3 v2:123 n2:55 d1/4 o48 o52 o55")
    def test_play_arpeggio(self):
        question = self.p.m_questions[0]
        question['music'].play_arpeggio(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(),
            "t180/4 p0:0 v0:100 n48 d1/4 o48 n52 d1/4 o52 n55 d1/4 o55")
        self.p.header.random_transpose = (True,)
        question['music'].play_arpeggio(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(),
            "t180/4 p0:0 v0:100 n50 d1/4 o50 n54 d1/4 o54 n57 d1/4 o57")
        cfg.set_bool('config/override_default_instrument', True)
        question['music'].play_arpeggio(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(),
            "t180/4 p0:1 v0:121 n50 d1/4 o50 p0:2 v0:122 n54 d1/4 o54 p0:3 v0:123 n57 d1/4 o57")
    def test_atonal_transpose(self):
        self.p.header.random_transpose = ('atonal', -4, 4)
        self.assertEquals(self.p.m_questions[0]['music'].get_musicdata_transposed(self.p), "d fis a")
        self.p.m_transpose = mpd.MusicalPitch.new_from_notename("es'")
        self.assertEquals(self.p.m_questions[0]['music'].get_musicdata_transposed(self.p), "ees g bes")

class TestMusic(unittest.TestCase):
    def setUp(self):
        cfg.set_bool('config/override_default_instrument', False)
        self.p = QuestionsLessonfile()
        self.p.parse_string("""
        header { random_transpose = no }
        question { music = music("\staff{c''}\staff{e'}\staff{c}") }
        question { 
            instrument = 33, 34, 35, 36, 37, 38
            music = music("\staff{c''}\staff{e'}\staff{c}")
        }
        question { 
            instrument = 33, 34, 35, 36, 37, 38
            music = music("\staff{c''}\staff{b'}")
        }
        question { 
            instrument = 33, 34, 35, 36, 37, 38
            music = music("\staff{c''}")
        }
        """)
        self.p.m_transpose = mpd.MusicalPitch.new_from_notename("d'")
        self.p._idx = 0
    def test_too_many_instruments_play(self):
        # If the question has two tracks, and the instrument variable
        # has more instruments, then only the first and last instrument
        # is used
        question = self.p.m_questions[2]
        question['music'].play(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(1),
            "t60/4 p0:37 v0:38 n0:72 p1:33 v1:34 n1:71 d1/4 o72 o71")
        # If the question has only one track, the last instrument,
        # representing the highest instrument, is used.
        question = self.p.m_questions[3]
        question['music'].play(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(1),
            "t60/4 p0:37 v0:38 n0:72 d1/4 o72")
    def test_play(self):
        question = self.p.m_questions[0]
        question['music'].play(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(1),
            "t60/4 p0:0 v0:100 n0:72 n0:64 n0:48 d1/4 o72 o64 o48")
        # Music objects should ignore config/override_default_instrument
        cfg.set_bool('config/override_default_instrument', True)
        question['music'].play(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(1),
            "t60/4 p0:0 v0:100 n0:72 n0:64 n0:48 d1/4 o72 o64 o48")
        #
        question = self.p.m_questions[1]
        # If the instrument variable it set, and it has more than one
        # instrument, as it as in questions[1], then even Music will
        # play with more than one instrument.:w
        question['music'].play(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(1),
            "t60/4 p0:37 v0:38 n0:72 p1:35 v1:36 n1:64 p2:33 v2:34 n2:48 d1/4 o72 o64 o48")
    def test_play_slowly(self):
        question = self.p.m_questions[0]
        #test.py TestMusic viser at det ikke blir generert riktig instrument
        #for ovelser som progressions-2 (harmonicprogressiondictation)
        question['music'].play_slowly(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(1),
            "t30/4 p0:0 v0:100 n0:72 n0:64 n0:48 d1/4 o72 o64 o48")
        cfg.set_bool('config/override_default_instrument', True)
        question['music'].play_slowly(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(1),
            "t30/4 p0:0 v0:100 n0:72 n0:64 n0:48 d1/4 o72 o64 o48")

class TestMusic3(unittest.TestCase):
    def setUp(self):
        cfg.set_bool('config/override_default_instrument', False)
        self.p = QuestionsLessonfile()
        self.p.parse_string("""
        header { random_transpose = no }
        question { music = music3("\staff{c''}\staff{e'}\staff{c}") }
        question {
            instrument = 33, 34, 35, 36, 37, 38
            music = music3("\staff{c''}\staff{g'}\staff{e'}\staff{c}")
        }
        """)
        self.p.m_transpose = mpd.MusicalPitch.new_from_notename("d'")
        self.p._idx = 0
    def test_play(self):
        question = self.p.m_questions[0]
        question['music'].play(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(1),
            "t60/4 p0:0 v0:100 n0:72 n0:64 n0:48 d1/4 o72 o64 o48")
        #
        cfg.set_bool('config/override_default_instrument', True)
        question['music'].play(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(1),
            "t60/4 p0:3 v0:123 n0:72 p1:2 v1:122 n1:64 p2:1 v2:121 n2:48 d1/4 o72 o64 o48")
        #
        question = self.p.m_questions[1]
        question['music'].play(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(1),
            "t60/4 p0:37 v0:38 n0:72 p1:35 v1:36 n1:67 n1:64 p2:33 v2:34 n2:48 d1/4 o72 o67 o64 o48")

class TestVoiceCommon(unittest.TestCase):
    def test_get_first_pitch(self):
        for s, n in (
            ("c d e", "c"),
            ("<c'' e>", "c''"),
            ("< c'' e>", "c''"),
            (" c,,16", "c,,"),
            (" \n c d e", "c"),
            (r"\clef bass c d e", "c"),
            (r"\clef violin \time 3/8 fis", "fis"),
            (r'\clef "violin_8" d', "d"),
            ("\\key g \\major \\time 2/4\n d'8 | [g g]", "d'"),
            ("\nc", "c"),
            ):
            x = Rvoice(s)
            self.assertEquals(x.get_first_pitch().get_octave_notename(), n)
        self.assertRaises(MusicObjectException, lambda: Rvoice("\\clef bass").get_first_pitch())


class TestVoice(LessonfileTestCase):
    def setUp(self):
        cfg.set_bool('config/override_default_instrument', False)
        self.p = QuestionsLessonfile()
        self.p.parse_string("""
        header { random_transpose = no }
        question { music = voice("c d e") }
        question { music = voice("< e") }
        """)
        self.p.m_transpose = mpd.MusicalPitch.new_from_notename("d'")
        self.p._idx = 0
    def test_get_mpd_music_string(self):
        self.assertEquals(self._m().get_mpd_music_string(self.p),
                          "\\staff{\nc d e\n}")
        self.p.header.random_transpose = [True]
        self.assertEquals(self._m().get_mpd_music_string(self.p),
                          "\\staff\\transpose d'{\nc d e\n}")
        self.p.header.random_transpose = ['atonal', -5, 6]
        self.assertEquals(self._m().get_mpd_music_string(self.p),
                          "\\staff{\nd e fis\n}")
    def test_get_lilypond_code(self):
        self.assertEquals(self._m().get_lilypond_code(self.p),
                          r"{ c d e }")
        self.p.header.random_transpose = [True]
        self.assertEquals(self._m().get_lilypond_code(self.p),
                          r"\transpose c' d'{ c d e }")
        self.p.header.random_transpose = "atonal", -5, 6
        self.assertEquals(self._m().get_lilypond_code(self.p),
                          r"{ d e fis }")
    def test_get_lilypond_code_first_note(self):
        self.assertEquals(self.p.m_questions[0]['music'].get_lilypond_code_first_note(self.p), r"\score{ \new Staff<< \new Voice{ \cadenzaOn c } \new Voice{ \hideNotes c d e } >> \layout { ragged-last = ##t } }")
        self.p.header.random_transpose = [True]
        self.assertEquals(self.p.m_questions[0]['music'].get_lilypond_code_first_note(self.p), ur"\score{ \new Staff<< \new Voice\transpose c' d'{ \cadenzaOn c } \new Voice{ \hideNotes c d e } >> \layout { ragged-last = ##t } }")
        self.p.header.random_transpose = "atonal", -5, 6
        self.assertEquals(self.p.m_questions[0]['music'].get_lilypond_code_first_note(self.p), r"\score{ \new Staff<< \new Voice{ \cadenzaOn d } \new Voice{ \hideNotes c d e } >> \layout { ragged-last = ##t } }")
    def _test_play(self):
        question = self.p.m_questions[0]
        question['music'].play(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(),
            "t60/4 p0:0 v0:100 n48 d1/4 o48 n50 d1/4 o50 n52 d1/4 o52")
    def test_play(self):
        self._test_play()
    def test_3patch_play(self):
        """
        voice music objects ignore config/override_default_instrument
        by design.
        """
        question = self.p.m_questions[0]
        self._test_play()
    def test_play_slowly(self):
        question = self.p.m_questions[0]
        question['music'].play_slowly(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(),
            "t30/4 p0:0 v0:100 n48 d1/4 o48 n50 d1/4 o50 n52 d1/4 o52")
    def test_findfirst(self):
        for p1, p2, s, n in (
            (0, 1, "c d e", "c"),
            (1, 4, "<c'' e>", "c''"),
            (2, 5, "< c'' e>", "c''"),
            (2, 5, "\n\nERR", "ERR"),
            (1, 4, " c,,16", "c,,"),
            (3, 4, " \n c d e", "c"),
            (11, 12, r"\clef bass c d e", "c"),
            (23, 26, r"\clef violin \time 3/8 fis", "fis"),
            (17, 18, r'\clef "violin_8" d', "d"),
            (25, 27, "\\key g \\major \\time 2/4\n d'8 | [g g]", "d'"),
            (1, 2, "\nc", "c"),
            ):
            x = Rvoice(s)
            self.assertEquals((p1, p2), mpdutils.find_possible_first_note(x.m_musicdata), "String failed: %s: %s" % (s, mpdutils.find_possible_first_note(x.m_musicdata)))
            self.assertEquals(s[p1:p2], n)
    def test_error_handling(self):
        m = self._m(1).get_mpd_music_string(self.p)
        try:
            mpd.parser.parse_to_score_object(m)
        except mpd.MpdException, e:
            self._m(1).complete_to_musicdata_coords(self.p, e)
            ec = self._m(1).get_err_context(e, self.p)
            self.assertEquals(ec, 
              "Bad input to the music object of type voice made\n"
              "the parser fail to parse the following generated\n"
              "music code:\n"
              "\\staff{\n"
              "< e\n"
              "}\n"
              "^")

        else:
            self.fail("Test failed. No exception raised.")
        return
        try:
            m = self._m(5).get_mpd_music_string(self.p)
        except mpd.MpdException, e:
            # We can not call complete_to_musicdata_coords becuase
            # get_mpd_music_string promises to always raise exceptions
            # relative to musicdata.
            self.assertEquals(self._m(5).get_err_context(e, self.p),
                "ERR f\n^^^")
        else:
            self.fail("Test failed. No exception raised.")
        self.p.header.random_transpose = ('atonal', -4, 4)
        try:
            m = self._m(5).get_mpd_music_string(self.p)
        except mpd.MpdException, e:
            self.assertEquals(self._m(5).get_err_context(e, self.p),
                "ERR f\n^^^")
        else:
            self.fail("Test failed. No exception raised.")

class TestErrorHandling(unittest.TestCase):
    def setUp(self):
        cfg.set_bool('config/override_default_instrument', False)
    def _dotest(self, musictypes, bad_code):
        for t in musictypes:
            for code, err_context in bad_code:
                self.p = QuestionsLessonfile()
                self.p.parse_string('''
                header { random_transpose = no }
                question { music = %s("%s") }
                ''' % (t, code))
                self.p.m_transpose = mpd.MusicalPitch.new_from_notename("d'")
                self.p._idx = 0
                question = self.p.m_questions[0]
                try:
                    self.p.play_question(question)
                except MusicObjectException, e:
                    self.assertEquals(err_context, str(e))
                except mpd.MpdException, e:
                    ec = question.music.get_err_context(e, self.p)
                    self.assertEquals(err_context, ec, "Music failed with musictype %s:\n%s !=\n%s" % (t, err_context, ec))
                else:
                    assert False, "No error found in code %s" % code
    def test_1(self):
        cfg.set_bool('config/override_default_instrument', False)
        self._test_1()
    def test_1_3patches(self):
        """
        Test with 3 different patches, just in case implementation details
        make error handling different with config/override_default_instrument
        set and unset.
        """
        cfg.set_bool('config/override_default_instrument', True)
        self._test_1()
    def _test_1(self):
        self._dotest(("chord", "voice", "rhythm", "percussion", "rhythm"),
                   (("c d e ERR f",
                     "c d e ERR f\n"
                     "      ^^^"),
                    ("c d e f\ng ERR a",
                    "g ERR a\n"
                     "  ^^^"),
                    ("c d XX e f\ng ERR a",
                     "c d XX e f\n"
                     "    ^^"),
                    ("ERR",
                     "ERR\n"
                     "^^^"),
                  ))
    def test_satb(self):
        self._test_satb()
    def test_satb_3patches(self):
        cfg.set_bool('config/override_default_instrument', True)
        self._test_satb()
    def _test_satb(self):
        self._dotest(("satb",),
            (("c'' | ERR | g | c",
              "c'' | ERR | g | c\n"
              "     ^^^^^"),
             ("c''|g'|ERR| c",
              "c''|g'|ERR| c\n"
              "       ^^^"),
             ("c'' |e' d'|\n ERR |c",
              "The music code from the lesson file has been modified by\n"
              "removing the new-line characters. This to more easily show\n"
              "where the error occured. Satb music should not contain music\n"
              "characters.\n"
              "c'' |e' d'| ERR |c\n"
              "           ^^^^^"),
             ("|||",
              "Satb music does not allow an empty voice"),
             ("{ | g",
              "Satb music should be divided into 4 parts by the '|' character",),
              ))
    def test_music(self):
        self._dotest(("music",),
            (("\\staff{ c' d ERR e }",
              "\\staff{ c' d ERR e }\n"
              "             ^^^"),
             ("\\staff{ c4. c. }",
              "\\staff{ c4. c. }\n"
              "            ^^"),
             ("\\addvoice{ jojo ",
              "\\addvoice{ jojo \n"
              "^^^^^^^^^"),
             ("\\staff { c4 c4. c. }",
              "\\staff { c4 c4. c. }\n"
              "                ^^"),
             ("\\staff { c4\n"
              "ERR }",
              "ERR }\n"
              "^^^"),
             ("\\staff{ \\clef ERROR c",
              "\\staff{ \\clef ERROR c\n"
                "        ^^^^^^^^^^^"),
            ))


class TestRvoice(LessonfileTestCase):
    def setUp(self):
        cfg.set_bool('config/override_default_instrument', False)
        self.p = QuestionsLessonfile()
        self.p.parse_string("""
        header { random_transpose = no }
        question { music = rvoice("c' d b c") }
        question { music = rvoice("[c'8 d] e16") }
        question { music = rvoice("c,, d' b c") }
        question { music = rvoice("b,, d' b c") }
        question { music = rvoice("< e") }
        question { music = rvoice("ERR") }
        question { music = rvoice("c d e f\ng ERR a") }
        """)
        self.p.m_transpose = mpd.MusicalPitch.new_from_notename("d'")
        self.p._idx = 0
    def test_get_mpd_music_string(self):
        self.assertEquals(self._m().get_mpd_music_string(self.p),
            "\\staff\\relative c'{\nc d b c\n}")
        self.p.header.random_transpose = (True,)
        self.assertEquals(self._m().get_mpd_music_string(self.p),
            "\\staff\\transpose d'\\relative c'{ c d b c }")
        self.p.header.random_transpose = ['atonal', -5, 6]
        self.assertEquals(self._m().get_mpd_music_string(self.p),
            "\\staff\\relative d'{\nd e cis d\n}")
        self.assertEquals(self._m(1).get_mpd_music_string(self.p),
            "\\staff\\relative d'{\n[ d8 e ] fis16\n}")
        self.assertEquals(self._m(2).get_mpd_music_string(self.p),
            "\\staff\\relative d,,{\nd e' cis d\n}")
        self.assertEquals(self._m(3).get_mpd_music_string(self.p),
            "\\staff\\relative cis,{\ncis e' cis d\n}")
    def _test_play(self):
        question = self.p.m_questions[0]
        question['music'].play(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(),
            "t60/4 p0:0 v0:100 n60 d1/4 o60 n62 d1/4 o62 n59 d1/4 o59 n60 d1/4 o60")
    def test_play(self):
        self._test_play()
    def test_3patch_play(self):
        """
        rvoice music objects ignore config/override_default_instrument
        by design.
        """
        question = self.p.m_questions[0]
        self._test_play()
    def test_play_slowly(self):
        question = self.p.m_questions[0]
        question['music'].play_slowly(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(),
            "t30/4 p0:0 v0:100 n60 d1/4 o60 n62 d1/4 o62 n59 d1/4 o59 n60 d1/4 o60")
    def test_get_lilypond_code(self):
        self.assertEquals(self.p.m_questions[0]['music'].get_lilypond_code(self.p), r"\transpose c' d'\relative c{ c' d b c }")
    def test_get_lilypond_code_first_note(self):
        self.assertEquals(self.p.m_questions[0]['music'].get_lilypond_code_first_note(self.p), r"\score{ \new Staff<< \new Voice\transpose c' d'\relative c{ \cadenzaOn c' } \new Voice{ \hideNotes c' d b c } >> \layout { ragged-last = ##t } }")
    def test_bad_first_note(self):
        self.p.header.random_transpose = (True, 0, 0)
        m = self._m(4).get_mpd_music_string(self.p)
        try:
            mpd.parser.parse_to_score_object(m)
        except mpd.parser.ParseError, e:
            self.assertEquals(self._m(4).get_err_context(e, self.p),
                "Bad input to the music object of type rvoice made\n"
                "the parser fail to parse the following generated\n"
                "music code:\n"
                "\\staff\\transpose d'\\relative e{ < e }\n"
                "                                    ^")
        else:
            self.fail("Test failed. No exception raised.")
        m = self._m(5).get_mpd_music_string(self.p)
        try:
            mpd.parser.parse_to_score_object(m)
        except mpd.InvalidNotenameException, e:
            self.assertEquals(self._m(5).get_err_context(e, self.p),
                "Bad input to the music object of type rvoice made\n"
                "the parser fail to parse the following generated\n"
                "music code:\n"
                "\\staff\\transpose d'\\relative ERR{ ERR }\n"
                "                   ^^^^^^^^^^^^^")
        else:
            self.fail("Test failed. No exception raised.")
        m = self._m(6).get_mpd_music_string(self.p)
        try:
            mpd.parser.parse_to_score_object(m)
        except mpd.InvalidNotenameException, e:
            self.assertEquals(self._m(6).get_err_context(e, self.p),
                "Bad input to the music object of type rvoice made\n"
                "the parser fail to parse the following generated\n"
                "music code:\n"
                "\\staff\\transpose d'\\relative c{ c d e f\n"
                "g ERR a }\n"
                "  ^^^")

class TestSatb(unittest.TestCase):
    def setUp(self):
        cfg.set_bool('config/override_default_instrument', False)
        self.p = QuestionsLessonfile()
        self.p.parse_string("""
        header { random_transpose = no }
        question { music = satb("c''| e'| g | c") }
        question { key = "aes \\major" music = satb("c''|as'|es'|as") }
        question { music = satb("c''| g' e'| g | c") }
        """)
        self.p.m_transpose = mpd.MusicalPitch.new_from_notename("d'")
        self.p._idx = 0
    def test_get_mpd_music_string(self):
        self.assertEquals(self.p.m_questions[0]['music'].get_mpd_music_string(self.p),
            "\\staff{ \\key c \\major\\stemUp <c''> }\n" \
            "\\addvoice{ \\stemDown <e'> }\n" \
            "\\staff{ \\key c \\major\\clef bass \\stemUp <g>}\n" \
            "\\addvoice{ \\stemDown <c>}")
        # FIXME Satb only works if the music object is the current selected
        # question. Can we get around this?
        self.p._idx  = 1
        self.assertEquals(self.p.m_questions[1]['music'].get_mpd_music_string(self.p),
            "\\staff{ \\key aes \\major\\stemUp <c''> }\n" \
            "\\addvoice{ \\stemDown <as'> }\n" \
            "\\staff{ \\key aes \\major\\clef bass \\stemUp <es'>}\n" \
            "\\addvoice{ \\stemDown <as>}")
    def test_play(self):
        question = self.p.m_questions[0]
        question['music'].play(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(),
            "t60/4 p0:0 v0:100 n72 n64 n55 n48 d1/4 o72 o64 o55 o48")
    def test_play_arpeggio(self):
        question = self.p.m_questions[0]
        question['music'].play_arpeggio(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(),
            "t60/4 p0:0 v0:100 n60 d1/4 o60 n52 d1/4 o52 n55 d1/4 o55 n48 d1/4 o48")
        question = self.p.m_questions[2]
        question['music'].play_arpeggio(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(),
            "t60/4 p0:0 v0:100 n60 d1/4 o60 n55 d1/4 o55 n52 d1/4 o52 n55 d1/4 o55 n48 d1/4 o48")
        # Then with transposition
        self.p.header.random_transpose = (True,)
        question['music'].play_arpeggio(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(),
            "t60/4 p0:0 v0:100 n62 d1/4 o62 n57 d1/4 o57 n54 d1/4 o54 n57 d1/4 o57 n50 d1/4 o50")

class TestRhythm(unittest.TestCase):
    def setUp(self):
        cfg.set_bool('config/override_default_instrument', False)
        self.p = QuestionsLessonfile()
        self.p.parse_string("""
        question { music = rhythm("d c8") }
        question { music = rhythm("c \time 3\4 c d") }
        """)
        self.p._idx = 0
    def test_play(self):
        question = self.p.m_questions[0]
        question['music'].play(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(),
            "t60/4 v9:100 P80 d1/4 o80 P37 d1/8 o37")
    def test_bad_time(self):
        question = self.p.m_questions[1]
        question.music.get_mpd_music_string(self.p)

class TestPercussion(unittest.TestCase):
    def setUp(self):
        cfg.set_bool('config/override_default_instrument', False)
        self.p = QuestionsLessonfile()
        self.p.parse_string("""
        question { music = percussion("e16 f g4") }
        """)
        self.p._idx = 0
    def test_play(self):
        question = self.p.m_questions[0]
        question['music'].play(self.p, question)
        self.assertEquals(soundcard.synth.flush_testdata(),
            "t60/4 v9:100 P52 d1/16 o52 P53 d1/16 o53 P55 d1/4 o55")


class TestWavfile(unittest.TestCase):
    def setUp(self):
        self.p = QuestionsLessonfile()
        self.p.parse_string("""
        question { music = wavfile("exercises/standard/lesson-files/share/fifth-small-293.33.wav") }
        """)
        self.p._idx = 0
    def test_play(self):
        question = self.p.m_questions[0]
        if cfg.get_bool('testing/may_play_sound'):
            question['music'].play(self.p, question)
            time.sleep(3)
    def test_get_mpd_music_string(self):
        question = self.p.m_questions[0]
        self.assertEquals(question['music'].get_mpd_music_string(self.p),
                          "Wavfile:exercises/standard/lesson-files/share/fifth-small-293.33.wav")
        self.assertEquals(question.music.m_musicdata,
                          "exercises/standard/lesson-files/share/fifth-small-293.33.wav")
        self.assertTrue(isinstance(question.music, Wavfile))

class TestMidifile(unittest.TestCase):
    def setUp(self):
        self.p = QuestionsLessonfile()
        self.p.parse_string("""
        question { music = midifile("exercises/standard/lesson-files/share/fanfare.midi") }
        """)
        self.p._idx = 0
    def test_play(self):
        question = self.p.m_questions[0]
        if cfg.get_bool('testing/may_play_sound'):
            question['music'].play(self.p, question)
            time.sleep(3)
    def test_get_mpd_music_string(self):
        question = self.p.m_questions[0]
        self.assertEquals(question['music'].get_mpd_music_string(self.p),
                          "Midifile:exercises/standard/lesson-files/share/fanfare.midi")
        self.assertEquals(question.music.m_musicdata,
                          "exercises/standard/lesson-files/share/fanfare.midi")
        self.assertTrue(isinstance(question.music, Midifile))


class TestCmdline(unittest.TestCase):
    def setUp(self):
        self.p = QuestionsLessonfile()
        self.p.parse_string("""
        question { music = cmdline("/usr/bin/play exercises/standard/lesson-files/share/fifth-pure-220.00.wav") }
        """)
        self.p._idx = 0
    def test_play(self):
        question = self.p.m_questions[0]
        if cfg.get_bool('testing/may_play_sound'):
            question['music'].play(self.p, question)
            time.sleep(3)

class TestCSound(unittest.TestCase):
    def test_CSound(self):
        self.p = QuestionsLessonfile()
        self.p.parse_string('''
header {
    lesson_id = "csound-intonation-M3-5cent"
    module = idbyname
    help = "idbyname-intonation"
    title = "Test "
}

question {
 name = "III temperature"
 set=0
 csound_sco = """
f1     0    4096 10   1  ; use GEN10 to compute a sine wave
; p1  p2    p3  p4     p5    p6     p7
;ins  strt  dur amp    freq  attack release
  i1  0     1   10000  440   0.2    0.08
  i1  +     1   10000  660   0.08    0.08
e
 """
 music = csound(load("exercises/standard/lesson-files/share/sinus-ad.orc"), csound_sco)
}''')
        question = self.p.m_questions[0]
        if cfg.get_bool('testing/may_play_sound'):
            question['music'].play(self.p, question)


class TestMma(unittest.TestCase):
    def test_1arg(self):
        s = """
        question {
            music = mma("1 Cmaj7")
        }
        """
        p = QuestionsLessonfile()
        p.parse_string(s)
        q = p.m_questions[0]
        if cfg.get_bool('testing/may_play_sound'):
            q['music'].play(p, q)
    def test_2arg(self):
        s = """
        question {
            music = mma("ballad", "1 Cmaj7")
        }
        """
        p = QuestionsLessonfile()
        p.parse_string(s)
        q = p.m_questions[0]
        if cfg.get_bool('testing/may_play_sound'):
            q['music'].play(p, q)


class TestLessonfileHeader(I18nSetup):
    def test_untranslated_variable(self):
        self.p = QuestionsLessonfile()
        self.p.parse_string("""
        header { untranslated = "jojo"
           translated = _("major")
        }
        question { } #dummy question to avoid NoQuestionsInFileException
        """)
        self.assertEquals(self.p.header['untranslated'], u"jojo")
        self.assertEquals(self.p.header['untranslated'].cval, u"jojo")
        self.assertEquals(self.p.header['translated'], u"dur")
        self.assertEquals(self.p.header['translated'].cval, u"major")
        self.assertEquals(self.p.header.untranslated, u"jojo")
        self.assertEquals(self.p.header.untranslated.cval, u"jojo")
        self.assertEquals(self.p.header.translated, u"dur")
        self.assertEquals(self.p.header.translated.cval, u"major")
    def test_infile_translations_discard(self):
        self.p = QuestionsLessonfile()
        self.p.parse_string("""
        header {
           title = "C-locale header"
           title[et] = "et-locale header"
        }
        question { } #dummy question to avoid NoQuestionsInFileException
        """)
        # This test is run with LANGUAGE='no', so the 'et' translation
        # is discarded
        self.assertEquals(self.p.header.title, "C-locale header")
        self.assertEquals(self.p.header.title.cval, "C-locale header")

class TestLessonfileQuestion(I18nSetup):
    def test_question_name(self):
        self.p = QuestionsLessonfile()
        self.p.parse_string("""
        question { name = "untranslated" }
        question { name = _("major") }
        question { # question has no name
        }
        """)
        self.assertEquals(self.p.m_questions[0].name, u"untranslated")
        self.assertEquals(self.p.m_questions[0].name.cval, u"untranslated")
        self.assertEquals(self.p.m_questions[1].name, u"dur")
        self.assertEquals(self.p.m_questions[1].name.cval, u"major")
        self.assertRaises(AttributeError, lambda : self.p.m_questions[2].name)
    def test_setattr(self):
        q = dataparser.Question()
        q.var = 1
        self.assertEquals(q.var, 1)
        self.assertEquals(q['var'], 1)

class TestLessonfileMisc(unittest.TestCase):
    def test_eq(self):
        c = Chord("a b c")
        d = Rvoice("a b c")
        e = Rvoice("a b c")
        f = Rvoice("c c c")
        self.assertNotEquals(c, d)
        self.assertNotEquals(d, e)
        self.assertNotEquals(d, f)
    def test_IdPropertyLessonfileFilter(self):
        s = """header {
                qprops = "name", "inversion"
                qprop_labels = _("Name"), _("Inversion")
            }
            question { name = "one" }
            question { name = "two" inversion=2 }
            """
        p = IdPropertyLessonfile()
        p.parse_string(s)
        # One question should be discarded because it does not have
        # the inversion variable
        self.assertEquals(len(p.m_discards), 1)
        self.assertEquals(len(p.m_questions), 1)
    def test_IdPropertyLessonfileFilter2(self):
        s = 'header { qprops = "name", "inversion" qprop_labels = "Name", "Inversion" }\n' \
            'question { name = "one" inversion=1} \n' \
            'question { name = "two" inversion=2 top=0 } '
        p = IdPropertyLessonfile()
        p.parse_string(s)
        # No questions are discaded.
        # top is not a property since it is not added to qprops, so
        # thats why the first question is not discarded.
        self.assertEquals(len(p.m_discards), 0)
        self.assertEquals(len(p.m_questions), 2)
    def test_IdPropertyLessonfile_qprops(self):
        s = 'header { qprops = "name", "inversion", "toptone" qprop_labels = "Name", "Inversion", "Toptone" }\n' \
            'question { name = "one" inversion=1} \n' \
            'question { name = "two" inversion=2 top=0 } '
        p = IdPropertyLessonfile()
        p.parse_string(s)
        # No questions are discarded, since they both contain the same
        # two properties, of the tree listed in qprops. The 'top' variable
        # in the second question does not affect what is or is not discarded
        # because is it not a property (not defined in qprops)
        self.assertEquals(len(p.m_questions), 2)
    def test_ChordLessonfileFilter(self):
        s = """header { }
            question { name = "one" }
            question { name = "two" inversion=2 }
            """
        p = ChordLessonfile()
        p.parse_string(s)
        # One question should be discarded because it does not have
        # the inversion variable
        self.assertEquals(len(p.m_questions), 1)
    def test_ChordLessonfileFilter2(self):
        s = 'header { qprops = "name", "inversion" qprop_labels = "Name", "Inversion" }\n' \
            'question { name = "one" inversion=1} \n' \
            'question { name = "two" inversion=2 top=0 } '
        p = ChordLessonfile()
        p.parse_string(s)
        # No questions are discaded.
        # top is not a property since it is not added to qprops, so
        # thats why the first question is not discarded.
        self.assertEquals(len(p.m_questions), 2)
    def test_IdProperty(self):
        """
        Once, a single item qprops did not work.
        """
        s = 'header { qprops = "name" qprop_labels = "NAME" } ' \
            'question { name = "major" music = chord("c e g") } '
        p = IdPropertyLessonfile()
        p.parse_string(s)
        self.assertEquals(p.header.qprops, [u'name'])
    def test_nrandom(self):
        self._worker_Xrandom("nrandom")
    def test_prandom(self):
        self._worker_Xrandom("prandom")
    def _worker_Xrandom(self, rfunc):
        styles = ["8beat", "ballad", "rock"]
        s = """
        styles = %s
        question {
            style = %s("8beat", "ballad")
        }
        question {
            style = %s(styles)
        }
        """ % (", ".join(['"%s"' % x for x in styles]), rfunc, rfunc)
        p = QuestionsLessonfile()
        p.parse_string(s)
        for idx in range(2):
            st = p.m_questions[idx]['style']
            for x in range(20):
                self.assert_(str(st) in styles)
                self.assert_(unicode(st) in styles)
                st.randomize()
            # This test only works with nrandom, because prandom will
            # return a possible different value every time.
            if rfunc == 'nrandom':
                l = gtk.Label(st)
                self.assert_(l.get_text() == unicode(st), "%s and gtk does not cooperate." % rfunc)

class TestLabelObject(unittest.TestCase):
    def test_construct(self):
        l = LabelObject("pangomarkup", "abc")
        self.assertEquals(l.cval, ("pangomarkup", "abc"))
    def test_rn_markup(self):
        for s, v in (
            ("Imaj7-", [('I', 'maj', '7', '-')]),
            ("bIIIm7 -", [('bIII', 'm', '7', ' -')]),
            ("Imaj7-IIm7-V9-Imaj7", [('I', 'maj', '7', '-'),
                                     ('II', 'm', '7', '-'),
                                     ('V', '', '9', '-'),
                                     ('I', 'maj', '7', '')]),
            ("Imaj7", [('I', 'maj', '7', '')]),
            ("iim9b5+13", [('ii', 'm', '9b5+13', '')]),
            ("Imaj7-V7", [('I', 'maj', '7', '-'), ('V', '', '7', '')]),
            ("Imaj7  -IIm9", [('I', 'maj', '7', '  -'), ('II', 'm', '9', '')]),
                ):
            self.assertEquals(rnc_markup_tokenizer(s), v, (rnc_markup_tokenizer(s), v))

    def test_chordname_markup(self):
        for s, v in (
            ("cis4", [('cis', '4', '', '')]),
            ('gmaj7', [('g', 'maj7', '', '')]),
            ('gesmaj7:9b5', [('ges', 'maj7', '9b5', '')]),
            ('g/g', [('g', '', '', 'g')]),
            ('g', [('g', '', '', '')]),
            ('gm', [('g', 'm', '', '')]),
            ('g:7', [('g', '', '7', '')]),
            ('gm:7', [('g', 'm', '7', '')]),
            ('d/fis', [('d', '', '', 'fis')]),
            ('gm/f', [('g', 'm', '', 'f')]),
            ('gm:7/d', [('g', 'm', '7', 'd')]),
            ('gm:13/5-/d', [('g', 'm', '13/5-', 'd')]),
            ):
            self.assertEquals(chordname_markup_tokenizer(s), v)


suite = unittest.makeSuite(TestParser)
suite.addTest(unittest.makeSuite(TestMpdTransposable))
suite.addTest(unittest.makeSuite(TestRhythmStaff))
suite.addTest(unittest.makeSuite(TestBar))
suite.addTest(unittest.makeSuite(TestChord))
suite.addTest(unittest.makeSuite(TestMusic))
suite.addTest(unittest.makeSuite(TestMusic3))
suite.addTest(unittest.makeSuite(TestVoiceCommon))
suite.addTest(unittest.makeSuite(TestVoice))
suite.addTest(unittest.makeSuite(TestRvoice))
suite.addTest(unittest.makeSuite(TestSatb))
suite.addTest(unittest.makeSuite(TestRhythm))
suite.addTest(unittest.makeSuite(TestPercussion))
suite.addTest(unittest.makeSuite(TestWavfile))
suite.addTest(unittest.makeSuite(TestMidifile))
suite.addTest(unittest.makeSuite(TestCmdline))
suite.addTest(unittest.makeSuite(TestCSound))
suite.addTest(unittest.makeSuite(TestMma))
suite.addTest(unittest.makeSuite(TestLessonfileHeader))
suite.addTest(unittest.makeSuite(TestLessonfileQuestion))
suite.addTest(unittest.makeSuite(TestLessonfileMisc))
suite.addTest(unittest.makeSuite(TestErrorHandling))
suite.addTest(unittest.makeSuite(TestLabelObject))
