1 """Unit tests for jazzparser.data module
2
3 """
4 """
5 ============================== License ========================================
6 Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding
7
8 This file is part of The Jazz Parser.
9
10 The Jazz Parser is free software: you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation, either version 3 of the License, or
13 (at your option) any later version.
14
15 The Jazz Parser is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with The Jazz Parser. If not, see <http://www.gnu.org/licenses/>.
22
23 ============================ End license ======================================
24
25 """
26 __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>"
27
28 import unittest
29 from jazzparser.data import Chord, DerivationTrace, Fraction
30
32 """
33 Tests for the various ways of creating instances of Chord.
34
35 """
36 ALLOWED_NUMERALS = [
37 ("C", 0, "C" ),
38 ("Db", 1, "Db"),
39 ("D", 2, "D" ),
40 ("Eb", 3, "Eb"),
41 ("E", 4, "E" ),
42 ("F", 5, "F" ),
43 ("F#", 6, "Gb"),
44 ("G", 7, "G" ),
45 ("G#", 8, "Ab"),
46 ("A", 9, "A" ),
47 ("Bb", 10, "Bb"),
48 ("B", 11, "B" ),
49 ]
50
52 """
53 Try creating chords using all possbile numerals and check the numeral
54 and root get set correctly.
55
56 """
57 for numeral,root,trg_num in self.ALLOWED_NUMERALS:
58
59 c = Chord(numeral)
60
61 self.assertEqual(trg_num, c.root_numeral)
62
63 self.assertEqual(root, c.root)
64
82
84 """
85 Try creating chords with a particular type and check (a) that they
86 successfully create a chord and (b) that the chord has the right type.
87
88 """
89 for ctype in Chord.TYPE_SYMBOLS.values():
90 c = Chord("C", type=ctype)
91 self.assertEqual(c.type, ctype)
92
94 """
95 Try getting the interval between two chords and check it comes out
96 as expected.
97
98 """
99
100 tests = [
101 (0, "C", "C", True),
102 (2, "F", "G", False),
103 (4, "D", "F#", False),
104 (6, "B", "F", True),
105 (8, "F", "Db", False),
106 (10, "Ab", "F#", False)
107 ]
108 for interval,lower,upper,invertible in tests:
109 c0 = Chord(lower)
110 c1 = Chord(upper)
111 self.assertEqual(interval, Chord.interval(c0,c1))
112
113
114 if invertible:
115 self.assertEqual(interval, Chord.interval(c1,c0))
116 else:
117 self.assertNotEqual(interval, Chord.interval(c1,c0))
118
120 """
121 from_name covers a lot of possible chord instances. Here we just test
122 a sample of textual chords and check the instance gets the right
123 attributes out of the name.
124 It's by no means exhaustive!
125
126 """
127 tests = [
128
129 ("C", 0, "", "", ""),
130 ("F#m7", 6, "m7", "", "m7"),
131 ("G7(9)", 7, "7", "9", "7"),
132 ("A(9)", 9, "", "9", "7"),
133 ("Dsus4", 2, "sus4", "", "sus4"),
134 ("Esus4,7", 4, "sus4,7","", "sus4,7"),
135 ("Esus4(9)", 4, "sus4", "9", "sus4,7"),
136 ("Fm,M7(+11)", 5, "m,M7", "+11", "m,M7"),
137 ]
138 for name,root,ctype,additions,tetrad in tests:
139 c = Chord.from_name(name)
140 self.assertEqual(root, c.root)
141 self.assertEqual(ctype, c.type)
142 self.assertEqual(additions, c.additions)
143 self.assertEqual(tetrad, c.tetrad_type)
144
145
147 """
148 A derivation trace is quite a simple data structure. We test that it
149 behaves correctly when used to store a trace of derivations in the
150 halfspan formalism.
151
152 This is specific to the L{halfspan
153 formalism<jazzparser.formalisms.music_halfspan>}, the current and
154 only supported formalism at the time
155 of writing. If the formalism is deprecated these tests will need to
156 be rewritten for the new formalism and these tests should be removed.
157
158 """
160 from jazzparser.formalisms.music_halfspan.rules import ApplicationRule
161 from jazzparser.formalisms.music_halfspan.syntax import AtomicCategory, \
162 ComplexCategory, HalfCategory, Sign, Slash
163 from jazzparser.formalisms.music_halfspan.semantics import \
164 DummyLogicalForm, Semantics
165 from jazzparser.grammar import Grammar
166
167
168 self.grammar = Grammar()
169
170
171 self.rule = self.grammar.rules_by_name['appf']
172
173
174 self.cat0 = AtomicCategory(
175 HalfCategory("I"),
176 HalfCategory("I") )
177
178 self.cat1 = ComplexCategory(
179 HalfCategory("V", function="D"),
180 Slash(True),
181 HalfCategory("I", function=["D","T"]) )
182
183 self.cat2 = AtomicCategory(
184 HalfCategory("V", function="D"),
185 HalfCategory("I") )
186
187
188 dummy_sem = Semantics(DummyLogicalForm())
189
190
191 self.sign0 = Sign(self.cat0, dummy_sem.copy())
192 self.sign1 = Sign(self.cat1, dummy_sem.copy())
193 self.sign2 = Sign(self.cat2, dummy_sem.copy())
194
196 """
197 Just creates a derivation trace in the simplest possible way, as if
198 it's a lexical production.
199
200 """
201 trace = DerivationTrace(self.sign0, word="IM7")
202
204 """
205 First creates two lexical traces (as tested in
206 L{test_create_lexical_trace}) and then a trace for applying the
207 application rule to them. The rule is not actually applied, we
208 just pretend it was.
209
210 """
211 trace0 = DerivationTrace(self.sign0, word="IM7")
212 trace1 = DerivationTrace(self.sign1, word="V7")
213
214 trace2 = DerivationTrace(self.sign2, rule=self.rule, args=[trace1, trace0])
215
217 """
218 Creates two derivation traces like that created in
219 L{test_create_rule_trace} and combines them into a single trace.
220
221 """
222 trace0 = DerivationTrace(self.sign0, word="IM7")
223 trace1 = DerivationTrace(self.sign1, word="V7")
224
225 trace2 = DerivationTrace(self.sign2, rule=self.rule, args=[trace1, trace0])
226
227
228 trace2.add_rule(self.rule, [trace1, trace0])
229
231 """
232 Does the same thing as L{test_multiple_source_trace}, but does it by
233 creating two DTs and adding the rules from one to the other.
234
235 """
236 trace0 = DerivationTrace(self.sign0, word="IM7")
237 trace1 = DerivationTrace(self.sign1, word="V7")
238
239 trace2 = DerivationTrace(self.sign2, rule=self.rule, args=[trace1, trace0])
240
241 trace2b = DerivationTrace(self.sign2, rule=self.rule, args=[trace1, trace0])
242 trace2.add_rules_from_trace(trace2b)
243
244
246 """
247 Tests for L{jazzparser.data.Fraction}.
248
249 """
251 """ Simplest instantiation: int """
252 f = Fraction(9)
253 self.assertEqual(f, 9)
254
256 """ Simplest instantiation: fraction """
257 f = Fraction(9, 10)
258
260 """
261 Basic test of simplification of fractions.
262
263 """
264 f0 = Fraction(9, 10)
265 f1 = Fraction(18, 20)
266 self.assertEqual(f0, f1)
267 f2 = Fraction(17, 20)
268 self.assertNotEqual(f0, f2)
269
271 """
272 Test creating a fraction from a string representation.
273
274 """
275 f0 = Fraction("1 1/4")
276 self.assertEqual(f0, Fraction(5, 4))
277 f1 = Fraction("5")
278 self.assertEqual(f1, Fraction(5))
279 f2 = Fraction("5/4")
280 self.assertEqual(f2, Fraction(5, 4))
281 for invalid in ["", "1.5", "1/1/1", "5 5", "4\\5", "a", "X"]:
282 self.assertRaises(Fraction.ValueError, Fraction, invalid)
283
285 """
286 Create some random fractions, get their string representation and check
287 the this can by used to correctly reinstantiate the fraction.
288
289 """
290 from random import randint
291 for i in range(50):
292
293 f0 = Fraction(randint(0,100), randint(1,100))
294 f0_str = str(f0)
295 self.assertEqual(f0, Fraction(f0_str))
296
298 """
299 Setting a Fraction's denominator to 0 should raise an error.
300
301 """
302 self.assertRaises(ZeroDivisionError, Fraction, 5, 0)
303 f = Fraction(1)
304 self.assertRaises(ZeroDivisionError, lambda x: f / x, 0)
305
307 """
308 Try adding Fractions together.
309
310 """
311 f0 = Fraction(5)
312 f1 = Fraction(6)
313 self.assertEqual(f0+f1, 11)
314 f2 = Fraction(7, 12) + f0
315 self.assertEqual(f2, Fraction("5 7/12"))
316 self.assertEqual(f2, Fraction(67, 12))
317
319 """ Try negating Fractions """
320 f0 = Fraction(5, 7)
321 self.assertEqual(-f0, Fraction(-5, 7))
322 f1 = Fraction("5 4/5")
323 self.assertEqual(-f1, Fraction("-5 4/5"))
324
326 """ Test subtraction """
327 f0 = Fraction(5)
328 f1 = Fraction(6)
329 self.assertEqual(f0-f1, -1)
330 f2 = Fraction(7, 12) - f0
331 self.assertEqual(f2, Fraction("-4 5/12"))
332 self.assertEqual(f2, Fraction(-53, 12))
333
335 """ Test multiplication """
336 f0 = Fraction(1,2) * Fraction(3)
337 self.assertEqual(f0, Fraction(3,2))
338 f1 = Fraction(5,7) * Fraction(3,9)
339 self.assertEqual(f1, Fraction(15, 63))
340 f2 = Fraction(-5,7) * Fraction(3,-9)
341 self.assertEqual(f2, f1)
342
344 """ Test division """
345 f0 = Fraction(1,2) / 3
346 self.assertEqual(f0, Fraction(1,6))
347 f0 = Fraction(1,2) / Fraction(3)
348 self.assertEqual(f0, Fraction(1,6))
349 f1 = Fraction(5,7) / Fraction(3,9)
350 self.assertEqual(f1, Fraction(45, 21))
351 f2 = Fraction(-5,7) / Fraction(3,-9)
352 self.assertEqual(f2, f1)
353 f3 = Fraction(5, 7) / 0.5
354 self.assertEqual(f3, 10.0/7.0)
355
357 """ Conversion to float """
358 from random import randint
359 for i in range(50):
360 n = randint(0, 100)
361 d = randint(1, 100)
362 self.assertEqual(float(Fraction(n,d)), float(n)/float(d))
363
365 """ Conversion to long """
366 from random import randint
367 for i in range(50):
368 n = randint(0, 100)
369 d = randint(1, 100)
370 self.assertEqual(long(Fraction(n,d)), long(n)/long(d))
371
373 """ Conversion to int """
374 from random import randint
375 for i in range(50):
376 n = randint(0, 100)
377 d = randint(1, 100)
378 self.assertEqual(int(Fraction(n,d)), n/d)
379
381 """ Test that equal Fractions evaluate as equal """
382 from random import randint
383 for i in range(10):
384 n = randint(0, 100)
385 d = randint(1, 100)
386 self.assertEqual(Fraction(n,d), Fraction(n,d))
387 self.assertEqual(Fraction(50,4), Fraction("12 1/2"))
388 self.assertEqual(Fraction(50,4), Fraction(25,2))
389 self.assertEqual(Fraction(7,6), --Fraction(7,6))
390 f = Fraction(7, 19)
391 self.assertEqual(f, f/Fraction(6, 17)*Fraction(12, 34))
392
393
394 if __name__ == '__main__':
395 unittest.main()
396