Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 439 lines (299 sloc) 18.641 kb
3c7ec57 @vbabiy starting to fill out the README
vbabiy authored
1 =================================
36132f2 @vbabiy Simple example in readme
vbabiy authored
2 Chai - Python Mocking Made Easy
3c7ec57 @vbabiy starting to fill out the README
vbabiy authored
3 =================================
4
31eb4bf @awestendorf UnexpectedCall is now a BaseException which is re-raised as an
awestendorf authored
5 :Version: 0.2.2
3c7ec57 @vbabiy starting to fill out the README
vbabiy authored
6 :Download: http://pypi.python.org/pypi/chai
7 :Source: https://github.com/agoragames/chai
8 :Keywords: python, mocking, testing, unittest, unittest2
9
10 .. contents::
11 :local:
12
13 .. _chai-overview:
14
15 Overview
16 ========
17
b47b272 @awestendorf README documents how chai works
awestendorf authored
18 Chai provides a very easy to use api for mocking/stubbing your python objects, patterned after the `Mocha <http://mocha.rubyforge.org/>`_ library for Ruby.
36132f2 @vbabiy Simple example in readme
vbabiy authored
19
3c7ec57 @vbabiy starting to fill out the README
vbabiy authored
20 .. _chai-example:
21
22 Example
23 =======
24
36132f2 @vbabiy Simple example in readme
vbabiy authored
25 The following is an example of a simple test case which is mocking out a get method
b47b272 @awestendorf README documents how chai works
awestendorf authored
26 on the ``CustomObject``. The Chai api allows use of call chains to make the code
06db058 @awestendorf Reflecting that we're releasing under the BSD license, and including it
awestendorf authored
27 short, clean, and very readable. It also does away with the standard setup-and-replay
28 work flow, giving you more flexibility in how you write your cases. ::
36132f2 @vbabiy Simple example in readme
vbabiy authored
29
30
31 from chai import Chai
32
33 class CustomObject (object):
34 def get(self, arg):
35 pass
36
37 class TestCase(Chai):
38 def test_mock_get(self):
39 obj = CustomObject()
40 self.expect(obj.get).args('name').returns('My Name')
41 self.assert_equals(obj.get('name'), 'My Name')
06db058 @awestendorf Reflecting that we're releasing under the BSD license, and including it
awestendorf authored
42 self.expect(obj.get).args('name').returns('Your Name')
43 self.assert_equals(obj.get('name'), 'Your Name')
36132f2 @vbabiy Simple example in readme
vbabiy authored
44
45 def test_mock_get_with_at_most(self):
46 obj = CustomObject()
47 self.expect(obj.get).args('name').returns('My Name').at_most(2)
48 self.assert_equals(obj.get('name'), 'My Name')
49 self.assert_equals(obj.get('name'), 'My Name')
50 self.assert_equals(obj.get('name'), 'My Name') # this one will fail
51
52 if __name__ == '__main__':
53 import unittest2
54 unittest2.main()
55
56
06db058 @awestendorf Reflecting that we're releasing under the BSD license, and including it
awestendorf authored
57 .. _chai-api:
58
59 API
60 ===
61
b47b272 @awestendorf README documents how chai works
awestendorf authored
62 All of the features are available by extending the ``Chai`` class, itself a subclass of ``unittest.TestCase``. If ``unittest2`` is available Chai will use that, else it will fall back to ``unittest``. Chai also aliases all of the ``assert*`` methods to lower-case with undersores. For example, ``assertNotEquals`` can also be referenced as ``assert_not_equals``.
06db058 @awestendorf Reflecting that we're releasing under the BSD license, and including it
awestendorf authored
63
0be4940 @awestendorf An example of local definitions inside a test case being loaded into …
awestendorf authored
64 Additionally, ``Chai`` loads in all assertions, comparators and mocking methods into the module in which a ``Chai`` subclass is declared. This is done to cut down on the verbosity of typing ``self.`` everywhere that you want to run a test. The references are loaded into the subclass' module during ``setUp``, so you're sure any method you call will be a reference to the class and module in which a particular test method is currently being executed. Methods and comparators you define locally in a test case will be globally available when you're running that particular case as well. ::
65
66 class ProtocolInterface(object):
67 def _private_call(self, arg):
68 pass
69 def get_result(self, arg):
70 self._private_call(arg)
71 return 'ok'
72
73 class TestCase(Chai):
74 def assert_complicated_state(self, obj):
75 return True # ..or.. raise AssertionError()
76
77 def test_mock_get(self):
78 obj = ProtocolInterface()
79 data = object()
80 expect(obj._private_call).args(data)
81 assert_equals('ok', obj.get_result(data))
82 assert_complicated_state(data)
06db058 @awestendorf Reflecting that we're releasing under the BSD license, and including it
awestendorf authored
83
b47b272 @awestendorf README documents how chai works
awestendorf authored
84 Stubbing
85 --------
06db058 @awestendorf Reflecting that we're releasing under the BSD license, and including it
awestendorf authored
86
b47b272 @awestendorf README documents how chai works
awestendorf authored
87 The simplest mock is to stub a method. This replaces the original method with a subclass of ``chai.Stub``, the main instrumentation class. All additional ``stub`` and ``expect`` calls will re-use this stub, and the stub is responsible for re-installing the original reference when ``Chai.tearDown`` is run.
88
0be4940 @awestendorf An example of local definitions inside a test case being loaded into …
awestendorf authored
89 Stubbing is used for situations when you want to assert that a method is never called. ::
b47b272 @awestendorf README documents how chai works
awestendorf authored
90
91 class CustomObject (object):
92 def get(self, arg):
93 pass
9372c36 @awestendorf Added to the functional test of properties. Added documentation to RE…
awestendorf authored
94 @property
95 def prop(self):
96 pass
b47b272 @awestendorf README documents how chai works
awestendorf authored
97
98 class TestCase(Chai):
99 def test_mock_get(self):
100 obj = CustomObject()
83df10f @awestendorf Stripped out self. refeferences and added documentation about global …
awestendorf authored
101 stub(obj.get)
102 assert_raises( UnexpectedCall, obj.get )
b47b272 @awestendorf README documents how chai works
awestendorf authored
103
0de2a3a @awestendorf Greatly simplified the mocking of object constructors
awestendorf authored
104 In this example, we can reference ``obj.get`` directly because ``get`` is a bound method and provides all of the context we need to refer back to ``obj`` and stub the method accordingly. There are cases where this is insufficient, such as module imports, special Python types, and when module attributes are imported from another (like ``os`` and ``posix``). If the object can't be stubbed with a reference, ``UnsupportedStub`` will be raised and you can use the verbose reference instead. ::
b47b272 @awestendorf README documents how chai works
awestendorf authored
105
106 class TestCase(Chai):
107 def test_mock_get(self):
108 obj = CustomObject()
83df10f @awestendorf Stripped out self. refeferences and added documentation about global …
awestendorf authored
109 stub(obj, 'get')
110 assert_raises( UnexpectedCall, obj.get )
b47b272 @awestendorf README documents how chai works
awestendorf authored
111
112 Stubbing an unbound method will apply that stub to all future instances of that class. ::
113
114 class TestCase(Chai):
115 def test_mock_get(self):
83df10f @awestendorf Stripped out self. refeferences and added documentation about global …
awestendorf authored
116 stub(CustomObject.get)
b47b272 @awestendorf README documents how chai works
awestendorf authored
117 obj = CustomObject()
83df10f @awestendorf Stripped out self. refeferences and added documentation about global …
awestendorf authored
118 assert_raises( UnexpectedCall, obj.get )
b47b272 @awestendorf README documents how chai works
awestendorf authored
119
6f52d88 @awestendorf Some additional readme help
awestendorf authored
120 Some methods cannot be stubbed because it is impossible to call ``setattr`` on the object, typically because it's a C extension. A good example of this is the ``datetime.datetime`` class. In that situation, it is best to mock out the entire module (see below).
9372c36 @awestendorf Added to the functional test of properties. Added documentation to RE…
awestendorf authored
121
122 Finally, Chai supports stubbing of properties on classes. In all cases, the stub will be applied to a class and individually to each of the 3 property methods. Because the stub is on the class, all instances need to be addressed when you write expectations. The first interface is via the named attribute method which can be used on both classes and instances. ::
123
124 class TestCase(Chai):
125 def test_prop_attr(self):
126 obj = CustomObject()
127 stub( obj, 'prop' )
128 assert_raises( UnexpectedCall, lambda: obj.prop )
129 stub( stub( obj, 'prop' ).setter )
130
131 Using the class, you can directly refer to all 3 methods of the property. To refer to the getter you use the property directly, and for the methods you use its associated attribute name. You can stub in any order and it will still resolve correctly. ::
132
133 class TestCase(Chai):
134 def test_prop_attr(self):
135 stub( CustomObject.prop.setter )
136 stub( CustomObject.prop )
137 stub( CustomObject.prop.deleter )
138 assert_raises( UnexpectedCall, lambda: CustomObject().prop )
b47b272 @awestendorf README documents how chai works
awestendorf authored
139
140
141 Expectation
142 -----------
143
144 Expectations are individual test cases that can be applied to a stub. They are expected to be run in order (unless otherwise noted). They are greedy, in that so long as an expectation has not been met and the arguments match, the arguments will be processed by that expectation. This mostly applies to the "at_least" and "any_order" expectations, which (may) stay open throughout the test and will handle any matching call.
145
146 Expectations will automatically create a stub if it's not already applied, so no separate call to ``stub`` is necessary. The arguments and edge cases regarding what can and cannot have expectations applied are identical to stubs. The ``expect`` call will return a new ``chai.Expectation`` object which can then be used to modify the expectation. Without any modifiers, an expectation will expect a single call without arguments and return None. ::
147
148 class TestCase(Chai):
149 def test_mock_get(self):
150 obj = CustomObject()
83df10f @awestendorf Stripped out self. refeferences and added documentation about global …
awestendorf authored
151 expect(obj.get)
152 assert_equals( None, obj.get() )
153 assert_raises( UnexpectedCall, obj.get )
b47b272 @awestendorf README documents how chai works
awestendorf authored
154
155 Modifiers can be applied to the expectation. Each modifier will return a reference to the expectation for easy chaining. In this example, we're going to match a parameter and change the behavior depending on the argument. This also shows the ability to incrementally add expectations throughout the test. ::
156
157 class TestCase(Chai):
158 def test_mock_get(self):
159 obj = CustomObject()
83df10f @awestendorf Stripped out self. refeferences and added documentation about global …
awestendorf authored
160 expect(obj.get).args('foo').returns('hello').times(2)
161 assert_equals( 'hello', obj.get('foo') )
162 assert_equals( 'hello', obj.get('foo') )
163 expect(obj.get).args('bar').raises( ValueError )
164 assert_raises( ValueError, obj.get, 'bar' )
b47b272 @awestendorf README documents how chai works
awestendorf authored
165
0de2a3a @awestendorf Greatly simplified the mocking of object constructors
awestendorf authored
166 It is very common to need to run expectations on the constructor for an object, possibly including returning a mock object. Chai makes this very simple. ::
167
168 def method():
169 obj = CustomObject('state')
170 obj.save()
171 return obj
172
173 class TestCase(Chai):
174 def test_method(self):
175 obj = mock()
176 expect( CustomObject ).args('state').returns( obj )
177 expect( obj.save )
178 assert_equals( obj, method() )
179
180
b47b272 @awestendorf README documents how chai works
awestendorf authored
181 Lastly, the arguments modifier supports several matching functions. For simplicity in covering the common cases, the arg expectation assumes an equals test for instances and an instanceof test for types. All rules that apply to positional arguments also apply to keyword arguments. ::
182
183 class TestCase(Chai):
184 def test_mock_get(self):
185 obj = CustomObject()
83df10f @awestendorf Stripped out self. refeferences and added documentation about global …
awestendorf authored
186 expect(obj.get).args(is_a(float)).returns(42)
187 assert_raises( UnexpectedCall, obj.get, 3 )
188 assert_equals( 42, obj.get(3.14) )
b47b272 @awestendorf README documents how chai works
awestendorf authored
189
83df10f @awestendorf Stripped out self. refeferences and added documentation about global …
awestendorf authored
190 expect(obj.get).args(str).returns('yes')
191 assert_equals( 'yes', obj.get('no') )
b47b272 @awestendorf README documents how chai works
awestendorf authored
192
83df10f @awestendorf Stripped out self. refeferences and added documentation about global …
awestendorf authored
193 expect(obj.get).args(is_arg(list)).return('yes')
194 assert_raises( UnexpectedCall, obj.get, [] )
195 assert_equals( 'yes', obj.get(list) )
b47b272 @awestendorf README documents how chai works
awestendorf authored
196
197 Modifiers
198 +++++++++
199
200 Expectations expose the following public methods for changing their behavior.
3c7ec57 @vbabiy starting to fill out the README
vbabiy authored
201
202
536ea57 @awestendorf Added "any_args" modifier
awestendorf authored
203 args(``*args``, ``**kwargs``)
b47b272 @awestendorf README documents how chai works
awestendorf authored
204 Add a test to the expectation for matching arguments.
36132f2 @vbabiy Simple example in readme
vbabiy authored
205
536ea57 @awestendorf Added "any_args" modifier
awestendorf authored
206 any_args
207 Any arguments are accepted.
208
b47b272 @awestendorf README documents how chai works
awestendorf authored
209 returns(object)
210 Add a return value to the expectation when it is matched and executed.
211
212 raises(exception)
213 When the expectation is run it will raise this exception. Accepts type or instance.
214
215 times(int)
216 An integer that defines a hard limit on the minimum and maximum number of times the expectation should be executed.
217
218 at_least(int)
219 Sets a minimum number of times the expectation should run and removes any maximum.
220
221 at_least_once
222 Equivalent to ``at_least(1)``.
223
224 at_most(int)
225 Sets a maximum number of times the expectation should run. Does not affect the minimum.
226
227 at_most_once
228 Equivalent to ``at_most(1)``.
229
230 once
231 Equivalent to ``times(1)``, also the default for any expectation.
232
233 any_order
234 The expectation can be called at any time, independent of when it was defined. Can be combined with ``at_least_once`` to force it to respond to all matching calls throughout the test.
6b6405a @awestendorf Documentation on side_effect and preparing 0.1.4
awestendorf authored
235
c457a8d @awestendorf chai 0.1.22
awestendorf authored
236 side_effect(callable, \*args, \*\*kwargs)
18160f0 @awestendorf side_effect is passed the arguments of the method call if it doesn't
awestendorf authored
237 Called with a function argument. When the expectation passes a test, the function will be executed. The side effect will be executed even if the expectation is configured to raise an exception. If the side effect is defined with arguments, then those arguments will be passed in when it's called, otherwise the arguments passed in to the expectation will be passed in.
b3ea7ca @awestendorf Added a teardown() modifier to expectations to allow them to be unstu…
awestendorf authored
238
239 teardown
240 Will remove the stub after the expectation has been met. This is useful in cases where you need to mock core methods such as ``open``, but immediately return its original behavior after the mocked call has run.
b47b272 @awestendorf README documents how chai works
awestendorf authored
241
242
7b0b03e @awestendorf Documentation regarding side effects when using the func() comparator
awestendorf authored
243 Argument Comparators
244 ++++++++++++++++++++
b47b272 @awestendorf README documents how chai works
awestendorf authored
245
0a4cfd4 @awestendorf More docs to help with recent changes
awestendorf authored
246 Argument comparators are defined as classes in ``chai.comparators``, but loaded into the ``Chai`` class for convenience (and by extension, a subclass' module). ``Chai`` handles the common case of a ``type`` object by using the ``is_a`` comparator, else defaults to the ``equals`` comparator. Users can create subclasses of ``Comparator`` and use those for custom argument processing.
247
248 Comparators can also be used inside data structures. For example: ::
249
250 expect( area ).args( {'pi':almost_equals(3.14), 'radius':is_a(int,long,float)} )
251
252
b47b272 @awestendorf README documents how chai works
awestendorf authored
253
254 equals(object)
255 The default comparator, uses standard Python equals operator
256
257 almost_equals(float, places)
258 Identical to assertAlmostEquals, will match an argument to the comparator value to a most ``places`` digits beyond the decimal point.
259
260 is_a(type)
b9bfbbe @awestendorf Updated documentation to match 0.1.5 API
awestendorf authored
261 Match an argument of a given type. Supports same arguments as builtin function ``isinstance``.
b47b272 @awestendorf README documents how chai works
awestendorf authored
262
263 is_arg(object)
264 Matches an argument using the Python ``is`` comparator.
265
266 any_of(comparator_list)
267 Matches an argument if any of the comparators in the argument list are met. Uses automatic comparator generation for instances and types in the list.
268
269 all_of(comparator_list)
270 Matches an argument if all of the comparators in the argument list are met. Uses automatic comparator generation for instances and types in the list.
271
272 not_of(comparator)
273 Matches an argument if the supplied comparator does not match.
274
275 matches(pattern)
276 Matches an argument using a regular expression. Standard ``re`` rules apply.
277
278 func(callable)
279 Matches an argument if the callable returns True. The callable must take one argument, the parameter being checked.
280
281 ignore
282 Matches any argument.
283
284 in_arg(in_list)
285 Matches if the argument is in the ``in_list``.
286
287 contains(object)
288 Matches if the argument contains the object using the Python ``in`` function.
289
536ea57 @awestendorf Added "any_args" modifier
awestendorf authored
290 like(container)
291 Matches if the argument contains all of the same items as in ``container``. Insists that the argument is the same type as ``container``. Useful when you need to assert a few values in a list or dictionary, but the exact contents are not known or can vary.
292
c83b63f @awestendorf Updated documentation, preparing 0.1.13 release
awestendorf authored
293 var(name)
e8d8528 @awestendorf Allow returning a variable so as to perform a regex-like capture.
awestendorf authored
294 A variable match against the first time that the argument is called. In the case of multiple calls, the second one must match the previous value of ``name``. After your tests have run, you can check the value against expected arguments through ``var(name).value``. This is really useful when you're testing a deep stack and it's simpler to assert that "value A was used in method call X". Variables can also be used to capture an argument and return it. ::
295
296 expect( encode ).args( var('src'), 'gzip' ).returns( var('src') )
c83b63f @awestendorf Updated documentation, preparing 0.1.13 release
awestendorf authored
297
b47b272 @awestendorf README documents how chai works
awestendorf authored
298
7b0b03e @awestendorf Documentation regarding side effects when using the func() comparator
awestendorf authored
299 **A note of caution**
300 If you are using the ``func`` comparator to produce side effects, be aware that it may be called more than once even if the expectation you're defining only occurs once. This is due to the way ``Stub.__call__`` processes the expectations and determines when to process arguments through an expectation.
301
f48990d @awestendorf Fixed a bug with (un)stubbing of a module attribute which is really a…
awestendorf authored
302
303 Context Manager
304 +++++++++++++++
305
306 An expectation can act as a context manager, which is very useful in complex mocking situations. The context will always be the return value for the expectation. For example: ::
307
308 def get_cursor(cname):
309 return db.Connection( 'host:port' ).collection( cname ).cursor()
310
311 def test_get_cursor():
312 with expect( db.Connection ).any_args().returns( mock() ) as connection:
313 with expect( connection.collection ).args( 'collection' ).returns( mock() ) as collection:
314 expect( collection.cursor ).returns( 'cursor' )
315
316 assert_equals( 'cursor', get_cursor('collection') )
317
b47b272 @awestendorf README documents how chai works
awestendorf authored
318 Mock
319 ----
320
321 Sometimes you need a mock object which can be used to stub and expect anything. Chai exposes this through the ``mock`` method which can be called in one of two ways.
322
323 Without any arguments, ``Chai.mock()`` will return a ``chai.Mock`` object that can be used for any purpose. If called with arguments, it behaves like ``stub`` and ``expect``, creating a Mock object and setting it as the attribute on another object.
324
73fd5d6 @awestendorf Added support for expectations on nested attributes of a Mock
awestendorf authored
325 Any request for an attribute from a Mock will return a new Mock object, but ``setattr`` behaves as expected so it can store state as well. The dynamic function will act like a stub, raising ``UnexpectedCall`` if no expectation is defined. ::
b47b272 @awestendorf README documents how chai works
awestendorf authored
326
327 class CustomObject(object):
328 def __init__(self, handle):
83df10f @awestendorf Stripped out self. refeferences and added documentation about global …
awestendorf authored
329 _handle = handle
b47b272 @awestendorf README documents how chai works
awestendorf authored
330 def do(self, arg):
83df10f @awestendorf Stripped out self. refeferences and added documentation about global …
awestendorf authored
331 return _handle.do(arg)
b47b272 @awestendorf README documents how chai works
awestendorf authored
332
333 class TestCase(Chai):
334 def test_mock_get(self):
83df10f @awestendorf Stripped out self. refeferences and added documentation about global …
awestendorf authored
335 obj = CustomObject( mock() )
0be4940 @awestendorf An example of local definitions inside a test case being loaded into …
awestendorf authored
336 expect( obj._handle.do ).args('it').returns('ok')
83df10f @awestendorf Stripped out self. refeferences and added documentation about global …
awestendorf authored
337 assert_equals('ok', obj.do('it'))
0be4940 @awestendorf An example of local definitions inside a test case being loaded into …
awestendorf authored
338 assert_raises( UnexpectedCall, obj._handle.do_it_again )
b47b272 @awestendorf README documents how chai works
awestendorf authored
339
73fd5d6 @awestendorf Added support for expectations on nested attributes of a Mock
awestendorf authored
340 The ``stub`` and ``expect`` methods handle ``Mock`` objects as arguments by mocking the ``__call__`` method, which can also act in place of ``__init__``. ::
b47b272 @awestendorf README documents how chai works
awestendorf authored
341
342 # module custom.py
343 from collections import deque
344
345 class CustomObject(object):
346 def __init__(self):
347 self._stack = deque()
348
349 # module custom_test.py
350 import custom
351 from custom import CustomObject
352
353 class TestCase(Chai):
354 def test_mock_get(self):
83df10f @awestendorf Stripped out self. refeferences and added documentation about global …
awestendorf authored
355 mock( custom, 'deque' )
356 expect( custom.deque ).returns( 'stack' )
b47b272 @awestendorf README documents how chai works
awestendorf authored
357
358 obj = CustomObject()
83df10f @awestendorf Stripped out self. refeferences and added documentation about global …
awestendorf authored
359 assert_equals('stack', obj._stack)
36132f2 @vbabiy Simple example in readme
vbabiy authored
360
6f52d88 @awestendorf Some additional readme help
awestendorf authored
361 Here we can see how to mock an entire module, in this case replacing the ``deque`` import in ``custom.py`` with a ``Mock``.
362
326742a @awestendorf Fixed typos
awestendorf authored
363 ``Mock`` objects, because of the ``getattr`` implementation, can also support nested attributes. ::
73fd5d6 @awestendorf Added support for expectations on nested attributes of a Mock
awestendorf authored
364
365 class TestCase(Chai):
366 def test_mock(self):
367 m = mock()
368 m.id = 42
369 expect( m.foo.bar ).returns( 'hello' )
370 assert_equals( 'hello', m.foo.bar() )
371 assert_equals( 42, m.id )
372
435f5b7 @awestendorf Chai 0.1.16
awestendorf authored
373 In addition to implementing ``__call__``, ``Mock`` objects implement ``__nonzero__``,
374 the container and context manager interfaces are defined. Nonzero will always return
375 ``True``; other methods will raise ``UnexpectedCall``. The ``__getattr__`` method
376 cannot be itself stubbed.
377
3c7ec57 @vbabiy starting to fill out the README
vbabiy authored
378 .. _chai-installation:
379
380 Installation
381 ============
382
383 You can install Chai either via the Python Package Index (PyPI)
384 or from source.
385
b47b272 @awestendorf README documents how chai works
awestendorf authored
386 To install using ``pip``,::
3c7ec57 @vbabiy starting to fill out the README
vbabiy authored
387
388 $ pip install chai
389
390 .. _chai-installing-from-source:
391
392 Downloading and installing from source
393 --------------------------------------
394
36132f2 @vbabiy Simple example in readme
vbabiy authored
395 Download the latest version of Chai from http://pypi.python.org/pypi/chai
3c7ec57 @vbabiy starting to fill out the README
vbabiy authored
396
397 You can install it by doing the following,::
398
399 $ tar xvfz chai-*.*.*.tar.gz
326742a @awestendorf Fixed typos
awestendorf authored
400 $ cd chai-*.*.*.tar.gz
909e4e7 @vbabiy Fixed readme
vbabiy authored
401 $ python setup.py install # as root
3c7ec57 @vbabiy starting to fill out the README
vbabiy authored
402
403 .. _chai-installing-from-git:
404
405 Using the development version
406 -----------------------------
407
408 You can clone the repository by doing the following::
409
410 $ git clone git://github.com/agoragames/chai.git
411
412 .. _bug-tracker:
413
414 Bug tracker
415 ===========
416
417 If you have any suggestions, bug reports or annoyances please report them
418 to our issue tracker at https://github.com/agoragames/chai/issues
419
420 .. _license:
421
422 License
423 =======
424
425 This software is licensed under the `New BSD License`. See the ``LICENSE``
426 file in the top distribution directory for the full license text.
427
605c258 @awestendorf chai 0.2.0
awestendorf authored
428 .. _contributors:
429
430 Contributors
431 ============
432
433 Special thank you to the following people for contributions to Chai
434
435 * Jason Baker (https://github.com/jasonbaker)
436
3c7ec57 @vbabiy starting to fill out the README
vbabiy authored
437 .. # vim: syntax=rst expandtab tabstop=4 shiftwidth=4 shiftround
438
Something went wrong with that request. Please try again.