##// END OF EJS Templates
Updates to basic notebook format....
Brian E. Granger -
Show More
@@ -1,79 +1,90 b''
1 """The basic dict based notebook format."""
1 """The basic dict based notebook format."""
2
2
3 import pprint
3 import pprint
4 import uuid
4 import uuid
5
5
6 from IPython.utils.ipstruct import Struct
6 from IPython.utils.ipstruct import Struct
7
7
8
8
9 class NotebookNode(Struct):
9 class NotebookNode(Struct):
10 pass
10 pass
11
11
12
12
13 def new_code_cell(input=None, prompt_number=None, output_text=None, output_png=None,
13 def new_output(output_type=None, output_text=None, output_png=None,
14 output_html=None, output_svg=None, output_latex=None, output_json=None,
14 output_html=None, output_svg=None, output_latex=None, output_json=None,
15 output_javascript=None):
15 output_javascript=None):
16 """Create a new code cell with input and output"""
16 """Create a new code cell with input and output"""
17 cell = NotebookNode()
18 cell.cell_type = 'code'
19 if input is not None:
20 cell.input = unicode(input)
21 if prompt_number is not None:
22 cell.prompt_number = int(prompt_number)
23
24 output = NotebookNode()
17 output = NotebookNode()
18 if output_type is not None:
19 output.output_type = unicode(output_type)
25 if output_text is not None:
20 if output_text is not None:
26 output.text = unicode(output_text)
21 output.text = unicode(output_text)
27 if output_png is not None:
22 if output_png is not None:
28 output.png = bytes(output_png)
23 output.png = bytes(output_png)
29 if output_html is not None:
24 if output_html is not None:
30 output.html = unicode(output_html)
25 output.html = unicode(output_html)
31 if output_svg is not None:
26 if output_svg is not None:
32 output.svg = unicode(output_svg)
27 output.svg = unicode(output_svg)
33 if output_latex is not None:
28 if output_latex is not None:
34 output.latex = unicode(output_latex)
29 output.latex = unicode(output_latex)
35 if output_json is not None:
30 if output_json is not None:
36 output.json = unicode(output_json)
31 output.json = unicode(output_json)
37 if output_javascript is not None:
32 if output_javascript is not None:
38 output.javascript = unicode(output_javascript)
33 output.javascript = unicode(output_javascript)
39
34
40 cell.output = output
35 return output
41 return cell
42
36
43
37
38 def new_code_cell(input=None, prompt_number=None, outputs=None, language=u'python'):
39 """Create a new code cell with input and output"""
40 cell = NotebookNode()
41 cell.cell_type = u'code'
42 if language is not None:
43 cell.language = unicode(language)
44 if input is not None:
45 cell.input = unicode(input)
46 if prompt_number is not None:
47 cell.prompt_number = int(prompt_number)
48 if outputs is None:
49 cell.outputs = []
50 else:
51 cell.outputs = outputs
52
53 return cell
54
44 def new_text_cell(text=None):
55 def new_text_cell(text=None):
45 """Create a new text cell."""
56 """Create a new text cell."""
46 cell = NotebookNode()
57 cell = NotebookNode()
47 if text is not None:
58 if text is not None:
48 cell.text = unicode(text)
59 cell.text = unicode(text)
49 cell.cell_type = 'text'
60 cell.cell_type = u'text'
50 return cell
61 return cell
51
62
52
63
53 def new_worksheet(name=None, cells=None):
64 def new_worksheet(name=None, cells=None):
54 """Create a worksheet by name with with a list of cells."""
65 """Create a worksheet by name with with a list of cells."""
55 ws = NotebookNode()
66 ws = NotebookNode()
56 if name is not None:
67 if name is not None:
57 ws.name = unicode(name)
68 ws.name = unicode(name)
58 if cells is None:
69 if cells is None:
59 ws.cells = []
70 ws.cells = []
60 else:
71 else:
61 ws.cells = list(cells)
72 ws.cells = list(cells)
62 return ws
73 return ws
63
74
64
75
65 def new_notebook(name=None, id=None, worksheets=None):
76 def new_notebook(name=None, id=None, worksheets=None):
66 """Create a notebook by name, id and a list of worksheets."""
77 """Create a notebook by name, id and a list of worksheets."""
67 nb = NotebookNode()
78 nb = NotebookNode()
68 if name is not None:
79 if name is not None:
69 nb.name = unicode(name)
80 nb.name = unicode(name)
70 if id is None:
81 if id is None:
71 nb.id = unicode(uuid.uuid4())
82 nb.id = unicode(uuid.uuid4())
72 else:
83 else:
73 nb.id = unicode(id)
84 nb.id = unicode(id)
74 if worksheets is None:
85 if worksheets is None:
75 nb.worksheets = []
86 nb.worksheets = []
76 else:
87 else:
77 nb.worksheets = list(worksheets)
88 nb.worksheets = list(worksheets)
78 return nb
89 return nb
79
90
@@ -1,141 +1,145 b''
1 """Read and write notebook files as XML."""
1 """Read and write notebook files as XML."""
2
2
3 from xml.etree import ElementTree as ET
3 from xml.etree import ElementTree as ET
4
4
5 from .rwbase import NotebookReader, NotebookWriter
5 from .rwbase import NotebookReader, NotebookWriter
6 from .nbbase import new_code_cell, new_text_cell, new_worksheet, new_notebook
6 from .nbbase import (
7
7 new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output
8 )
8
9
9 def indent(elem, level=0):
10 def indent(elem, level=0):
10 i = "\n" + level*" "
11 i = "\n" + level*" "
11 if len(elem):
12 if len(elem):
12 if not elem.text or not elem.text.strip():
13 if not elem.text or not elem.text.strip():
13 elem.text = i + " "
14 elem.text = i + " "
14 if not elem.tail or not elem.tail.strip():
15 if not elem.tail or not elem.tail.strip():
15 elem.tail = i
16 elem.tail = i
16 for elem in elem:
17 for elem in elem:
17 indent(elem, level+1)
18 indent(elem, level+1)
18 if not elem.tail or not elem.tail.strip():
19 if not elem.tail or not elem.tail.strip():
19 elem.tail = i
20 elem.tail = i
20 else:
21 else:
21 if level and (not elem.tail or not elem.tail.strip()):
22 if level and (not elem.tail or not elem.tail.strip()):
22 elem.tail = i
23 elem.tail = i
23
24
24
25
25 def _get_text(e, tag):
26 def _get_text(e, tag):
26 sub_e = e.find(tag)
27 sub_e = e.find(tag)
27 if sub_e is None:
28 if sub_e is None:
28 return None
29 return None
29 else:
30 else:
30 return sub_e.text
31 return sub_e.text
31
32
32
33
34 def _set_text(nbnode, attr, parent, tag):
35 if attr in nbnode:
36 e = ET.SubElement(parent, tag)
37 e.text = nbnode[attr]
38
39
40 def _get_int(e, tag):
41 sub_e = e.find(tag)
42 if sub_e is None:
43 return None
44 else:
45 return int(sub_e.text)
46
47
48 def _set_int(nbnode, attr, parent, tag):
49 if attr in nbnode:
50 e = ET.SubElement(parent, tag)
51 e.text = unicode(nbnode[attr])
52
53
33 class XMLReader(NotebookReader):
54 class XMLReader(NotebookReader):
34
55
35 def reads(self, s, **kwargs):
56 def reads(self, s, **kwargs):
36 root = ET.fromstring(s)
57 root = ET.fromstring(s)
37
58
38 nbname = _get_text(root,'name')
59 nbname = _get_text(root,'name')
39 nbid = _get_text(root,'id')
60 nbid = _get_text(root,'id')
40
61
41 worksheets = []
62 worksheets = []
42 for ws_e in root.getiterator('worksheet'):
63 for ws_e in root.find('worksheets').getiterator('worksheet'):
43 wsname = _get_text(ws_e,'name')
64 wsname = _get_text(ws_e,'name')
44 cells = []
65 cells = []
45 for cell_e in ws_e.getiterator():
66 for cell_e in ws_e.find('cells').getiterator():
46 if cell_e.tag == 'codecell':
67 if cell_e.tag == 'codecell':
47 input = _get_text(cell_e,'input')
68 input = _get_text(cell_e,'input')
48 output_e = cell_e.find('output')
69 prompt_number = _get_int(cell_e,'prompt_number')
49 if output_e is not None:
70 language = _get_text(cell_e,'language')
71 outputs = []
72 for output_e in cell_e.find('outputs').getiterator('output'):
73 output_type = _get_text(output_e,'output_type')
50 output_text = _get_text(output_e,'text')
74 output_text = _get_text(output_e,'text')
51 output_png = _get_text(output_e,'png')
75 output_png = _get_text(output_e,'png')
52 output_svg = _get_text(output_e,'svg')
76 output_svg = _get_text(output_e,'svg')
53 output_html = _get_text(output_e,'html')
77 output_html = _get_text(output_e,'html')
54 output_latex = _get_text(output_e,'latex')
78 output_latex = _get_text(output_e,'latex')
55 output_json = _get_text(output_e,'json')
79 output_json = _get_text(output_e,'json')
56 output_javascript = _get_text(output_e,'javascript')
80 output_javascript = _get_text(output_e,'javascript')
57 cc = new_code_cell(input=input,output_png=output_png,
81 output = new_output(output_type=output_type,output_png=output_png,
58 output_text=output_text,output_svg=output_svg,
82 output_text=output_text,output_svg=output_svg,
59 output_html=output_html,output_latex=output_latex,
83 output_html=output_html,output_latex=output_latex,
60 output_json=output_json,output_javascript=output_javascript
84 output_json=output_json,output_javascript=output_javascript
61 )
85 )
86 outputs.append(output)
87 cc = new_code_cell(input=input,prompt_number=prompt_number,
88 language=language,outputs=outputs)
62 cells.append(cc)
89 cells.append(cc)
63 if cell_e.tag == 'textcell':
90 if cell_e.tag == 'textcell':
64 text = _get_text(cell_e,'text')
91 text = _get_text(cell_e,'text')
65 cells.append(new_text_cell(text=text))
92 cells.append(new_text_cell(text=text))
66 ws = new_worksheet(name=wsname,cells=cells)
93 ws = new_worksheet(name=wsname,cells=cells)
67 worksheets.append(ws)
94 worksheets.append(ws)
68
95
69 nb = new_notebook(name=nbname,id=nbid,worksheets=worksheets)
96 nb = new_notebook(name=nbname,id=nbid,worksheets=worksheets)
70 return nb
97 return nb
71
98
72
99
73 class XMLWriter(NotebookWriter):
100 class XMLWriter(NotebookWriter):
74
101
75 def writes(self, nb, **kwargs):
102 def writes(self, nb, **kwargs):
76 nb_e = ET.Element('notebook')
103 nb_e = ET.Element('notebook')
77 if 'name' in nb:
104 _set_text(nb,'name',nb_e,'name')
78 name_e = ET.SubElement(nb_e, 'name')
105 _set_text(nb,'id',nb_e,'id')
79 name_e.text = nb.name
106 wss_e = ET.SubElement(nb_e,'worksheets')
80 if 'id' in nb:
81 id_e = ET.SubElement(nb_e, 'id')
82 id_e.text = nb.id
83 for ws in nb.worksheets:
107 for ws in nb.worksheets:
84 ws_e = ET.SubElement(nb_e, 'worksheet')
108 ws_e = ET.SubElement(wss_e, 'worksheet')
85 if 'name' in ws:
109 _set_text(ws,'name',ws_e,'name')
86 ws_name_e = ET.SubElement(ws_e, 'name')
110 cells_e = ET.SubElement(ws_e,'cells')
87 ws_name_e.text = ws.name
88 for cell in ws.cells:
111 for cell in ws.cells:
89 cell_type = cell.cell_type
112 cell_type = cell.cell_type
90 if cell_type == 'code':
113 if cell_type == 'code':
91 output = cell.output
114 cell_e = ET.SubElement(cells_e, 'codecell')
92 cell_e = ET.SubElement(ws_e, 'codecell')
115 _set_text(cell,'input',cell_e,'input')
93 output_e = ET.SubElement(cell_e, 'output')
116 _set_text(cell,'language',cell_e,'language')
94
117 _set_int(cell,'prompt_number',cell_e,'prompt_number')
95 if 'input' in cell:
118 outputs_e = ET.SubElement(cell_e, 'outputs')
96 input_e = ET.SubElement(cell_e, 'input')
119 for output in cell.outputs:
97 input_e.text = cell.input
120 output_e = ET.SubElement(outputs_e, 'output')
98 if 'prompt_number' in cell:
121 _set_text(output,'output_type',output_e,'output_type')
99 prompt_number_e = ET.SubElement(cell_e, 'prompt_number')
122 _set_text(output,'text',output_e,'text')
100 input_e.text = cell.prompt_number
123 _set_text(output,'png',output_e,'png')
101
124 _set_text(output,'html',output_e,'html')
102 if 'text' in output:
125 _set_text(output,'svg',output_e,'svg')
103 text_e = ET.SubElement(output_e, 'text')
126 _set_text(output,'latex',output_e,'latex')
104 text_e.text = output.text
127 _set_text(output,'json',output_e,'json')
105 if 'png' in output:
128 _set_text(output,'javascript',output_e,'javascript')
106 png_e = ET.SubElement(output_e, 'png')
107 png_e.text = output.png
108 if 'html' in output:
109 html_e = ET.SubElement(output_e, 'html')
110 html_e.text = output.html
111 if 'svg' in output:
112 svg_e = ET.SubElement(output_e, 'svg')
113 svg_e.text = output.svg
114 if 'latex' in output:
115 latex_e = ET.SubElement(output_e, 'latex')
116 latex_e.text = output.latex
117 if 'json' in output:
118 json_e = ET.SubElement(output_e, 'json')
119 json_e.text = output.json
120 if 'javascript' in output:
121 javascript_e = ET.SubElement(output_e, 'javascript')
122 javascript_e.text = output.javascript
123 elif cell_type == 'text':
129 elif cell_type == 'text':
124 cell_e = ET.SubElement(ws_e, 'textcell')
130 cell_e = ET.SubElement(cells_e, 'textcell')
125 if 'text' in cell:
131 _set_text(cell,'text',cell_e,'text')
126 cell_text_e = ET.SubElement(cell_e, 'text')
127 cell_text_e.text = cell.text
128
132
129 indent(nb_e)
133 indent(nb_e)
130 txt = ET.tostring(nb_e, encoding="utf-8")
134 txt = ET.tostring(nb_e, encoding="utf-8")
131 txt = '<?xml version="1.0" encoding="utf-8"?>\n' + txt
135 txt = '<?xml version="1.0" encoding="utf-8"?>\n' + txt
132 return txt
136 return txt
133
137
134
138
135 _reader = XMLReader()
139 _reader = XMLReader()
136 _writer = XMLWriter()
140 _writer = XMLWriter()
137
141
138 reads = _reader.reads
142 reads = _reader.reads
139 read = _reader.read
143 read = _reader.read
140 write = _writer.write
144 write = _writer.write
141 writes = _writer.writes
145 writes = _writer.writes
@@ -1,52 +1,67 b''
1 from IPython.nbformat.nbbase import (
1 from IPython.nbformat.nbbase import (
2 NotebookNode,
2 NotebookNode,
3 new_code_cell, new_text_cell, new_worksheet, new_notebook
3 new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output
4 )
4 )
5
5
6
6
7
7
8 ws = new_worksheet(name='worksheet1')
8 ws = new_worksheet(name='worksheet1')
9
9
10 ws.cells.append(new_text_cell(
10 ws.cells.append(new_text_cell(
11 text='Some NumPy Examples'
11 text='Some NumPy Examples'
12 ))
12 ))
13
13
14
14
15 ws.cells.append(new_code_cell(
15 ws.cells.append(new_code_cell(
16 input='import numpy'
16 input='import numpy',
17 prompt_number=1
17 ))
18 ))
18
19
19 ws.cells.append(new_code_cell(
20 ws.cells.append(new_code_cell(
20 input='a = numpy.random.rand(100)'
21 input='a = numpy.random.rand(100)',
22 prompt_number=2
21 ))
23 ))
22
24
23 ws.cells.append(new_code_cell(
25 ws.cells.append(new_code_cell(
24 input='print a',
26 input='print a',
25 output_text='<array a>',
27 prompt_number=3,
26 output_html='The HTML rep',
28 outputs=[new_output(
27 output_latex='$a$',
29 output_type=u'pyout',
28 output_png=b'data',
30 output_text=u'<array a>',
29 output_svg='<svg>',
31 output_html=u'The HTML rep',
30 output_json='json data',
32 output_latex=u'$a$',
31 output_javascript='var i=0;'
33 output_png=b'data',
34 output_svg=u'<svg>',
35 output_json=u'json data',
36 output_javascript=u'var i=0;'
37 ),new_output(
38 output_type=u'display_data',
39 output_text=u'<array a>',
40 output_html=u'The HTML rep',
41 output_latex=u'$a$',
42 output_png=b'data',
43 output_svg=u'<svg>',
44 output_json=u'json data',
45 output_javascript=u'var i=0;'
46 )]
32 ))
47 ))
33
48
34 nb0 = new_notebook(
49 nb0 = new_notebook(
35 name='nb0',
50 name='nb0',
36 worksheets=[ws, new_worksheet(name='worksheet2')]
51 worksheets=[ws, new_worksheet(name='worksheet2')]
37 )
52 )
38
53
39 nb0_py = """# <codecell>
54 nb0_py = """# <codecell>
40
55
41 import numpy
56 import numpy
42
57
43 # <codecell>
58 # <codecell>
44
59
45 a = numpy.random.rand(100)
60 a = numpy.random.rand(100)
46
61
47 # <codecell>
62 # <codecell>
48
63
49 print a
64 print a
50 """
65 """
51
66
52
67
@@ -1,60 +1,62 b''
1 from unittest import TestCase
1 from unittest import TestCase
2
2
3 from IPython.nbformat.nbbase import (
3 from IPython.nbformat.nbbase import (
4 NotebookNode,
4 NotebookNode,
5 new_code_cell, new_text_cell, new_worksheet, new_notebook
5 new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output
6 )
6 )
7
7
8 class TestCell(TestCase):
8 class TestCell(TestCase):
9
9
10 def test_empty_code_cell(self):
10 def test_empty_code_cell(self):
11 cc = new_code_cell()
11 cc = new_code_cell()
12 self.assertEquals(cc.cell_type,'code')
12 self.assertEquals(cc.cell_type,'code')
13 self.assertEquals('input' not in cc, True)
13 self.assertEquals('input' not in cc, True)
14 self.assertEquals('prompt_number' not in cc, True)
14 self.assertEquals('prompt_number' not in cc, True)
15 self.assertEquals(cc.output, NotebookNode())
15 self.assertEquals(cc.outputs, [])
16
16
17 def test_code_cell(self):
17 def test_code_cell(self):
18 cc = new_code_cell(input='a=10', prompt_number=0, output_svg='foo', output_text='10')
18 cc = new_code_cell(input='a=10', prompt_number=0)
19 cc.outputs = [new_output(output_type='pyout',output_svg='foo',output_text='10')]
19 self.assertEquals(cc.input, u'a=10')
20 self.assertEquals(cc.input, u'a=10')
20 self.assertEquals(cc.prompt_number, 0)
21 self.assertEquals(cc.prompt_number, 0)
21 self.assertEquals(cc.output.svg, u'foo')
22 self.assertEquals(cc.language, u'python')
22 self.assertEquals(cc.output.text, u'10')
23 self.assertEquals(cc.outputs[0].svg, u'foo')
24 self.assertEquals(cc.outputs[0].text, u'10')
23
25
24 def test_empty_text_cell(self):
26 def test_empty_text_cell(self):
25 tc = new_text_cell()
27 tc = new_text_cell()
26 self.assertEquals(tc.cell_type, 'text')
28 self.assertEquals(tc.cell_type, 'text')
27 self.assertEquals('text' not in tc, True)
29 self.assertEquals('text' not in tc, True)
28
30
29 def test_text_cell(self):
31 def test_text_cell(self):
30 tc = new_text_cell('hi')
32 tc = new_text_cell('hi')
31 self.assertEquals(tc.text, u'hi')
33 self.assertEquals(tc.text, u'hi')
32
34
33
35
34 class TestWorksheet(TestCase):
36 class TestWorksheet(TestCase):
35
37
36 def test_empty_worksheet(self):
38 def test_empty_worksheet(self):
37 ws = new_worksheet()
39 ws = new_worksheet()
38 self.assertEquals(ws.cells,[])
40 self.assertEquals(ws.cells,[])
39 self.assertEquals('name' not in ws, True)
41 self.assertEquals('name' not in ws, True)
40
42
41 def test_worksheet(self):
43 def test_worksheet(self):
42 cells = [new_code_cell(), new_text_cell()]
44 cells = [new_code_cell(), new_text_cell()]
43 ws = new_worksheet(cells=cells,name='foo')
45 ws = new_worksheet(cells=cells,name='foo')
44 self.assertEquals(ws.cells,cells)
46 self.assertEquals(ws.cells,cells)
45 self.assertEquals(ws.name,u'foo')
47 self.assertEquals(ws.name,u'foo')
46
48
47 class TestNotebook(TestCase):
49 class TestNotebook(TestCase):
48
50
49 def test_empty_notebook(self):
51 def test_empty_notebook(self):
50 nb = new_notebook()
52 nb = new_notebook()
51 self.assertEquals('id' in nb, True)
53 self.assertEquals('id' in nb, True)
52 self.assertEquals(nb.worksheets, [])
54 self.assertEquals(nb.worksheets, [])
53 self.assertEquals('name' not in nb, True)
55 self.assertEquals('name' not in nb, True)
54
56
55 def test_notebooke(self):
57 def test_notebooke(self):
56 worksheets = [new_worksheet(),new_worksheet()]
58 worksheets = [new_worksheet(),new_worksheet()]
57 nb = new_notebook(name='foo',worksheets=worksheets)
59 nb = new_notebook(name='foo',worksheets=worksheets)
58 self.assertEquals(nb.name,u'foo')
60 self.assertEquals(nb.name,u'foo')
59 self.assertEquals(nb.worksheets,worksheets)
61 self.assertEquals(nb.worksheets,worksheets)
60
62
@@ -1,12 +1,18 b''
1 from unittest import TestCase
1 from unittest import TestCase
2
2
3 from IPython.nbformat.nbxml import reads, writes
3 from IPython.nbformat.nbxml import reads, writes
4 from IPython.nbformat.tests.nbexamples import nb0
4 from IPython.nbformat.tests.nbexamples import nb0
5
5 import pprint
6
6
7 class TestXML(TestCase):
7 class TestXML(TestCase):
8
8
9 def test_roundtrip(self):
9 def test_roundtrip(self):
10 s = writes(nb0)
10 s = writes(nb0)
11 # print
12 # print pprint.pformat(nb0,indent=2)
13 # print
14 # print pprint.pformat(reads(s),indent=2)
15 # print
16 # print s
11 self.assertEquals(reads(s),nb0)
17 self.assertEquals(reads(s),nb0)
12
18
@@ -1,443 +1,443 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """IPython Test Suite Runner.
2 """IPython Test Suite Runner.
3
3
4 This module provides a main entry point to a user script to test IPython
4 This module provides a main entry point to a user script to test IPython
5 itself from the command line. There are two ways of running this script:
5 itself from the command line. There are two ways of running this script:
6
6
7 1. With the syntax `iptest all`. This runs our entire test suite by
7 1. With the syntax `iptest all`. This runs our entire test suite by
8 calling this script (with different arguments) recursively. This
8 calling this script (with different arguments) recursively. This
9 causes modules and package to be tested in different processes, using nose
9 causes modules and package to be tested in different processes, using nose
10 or trial where appropriate.
10 or trial where appropriate.
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 the script simply calls nose, but with special command line flags and
12 the script simply calls nose, but with special command line flags and
13 plugins loaded.
13 plugins loaded.
14
14
15 """
15 """
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Copyright (C) 2009 The IPython Development Team
18 # Copyright (C) 2009 The IPython Development Team
19 #
19 #
20 # Distributed under the terms of the BSD License. The full license is in
20 # Distributed under the terms of the BSD License. The full license is in
21 # the file COPYING, distributed as part of this software.
21 # the file COPYING, distributed as part of this software.
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Imports
25 # Imports
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 # Stdlib
28 # Stdlib
29 import os
29 import os
30 import os.path as path
30 import os.path as path
31 import signal
31 import signal
32 import sys
32 import sys
33 import subprocess
33 import subprocess
34 import tempfile
34 import tempfile
35 import time
35 import time
36 import warnings
36 import warnings
37
37
38 # Note: monkeypatch!
38 # Note: monkeypatch!
39 # We need to monkeypatch a small problem in nose itself first, before importing
39 # We need to monkeypatch a small problem in nose itself first, before importing
40 # it for actual use. This should get into nose upstream, but its release cycle
40 # it for actual use. This should get into nose upstream, but its release cycle
41 # is slow and we need it for our parametric tests to work correctly.
41 # is slow and we need it for our parametric tests to work correctly.
42 from IPython.testing import nosepatch
42 from IPython.testing import nosepatch
43 # Now, proceed to import nose itself
43 # Now, proceed to import nose itself
44 import nose.plugins.builtin
44 import nose.plugins.builtin
45 from nose.core import TestProgram
45 from nose.core import TestProgram
46
46
47 # Our own imports
47 # Our own imports
48 from IPython.utils.path import get_ipython_module_path
48 from IPython.utils.path import get_ipython_module_path
49 from IPython.utils.process import find_cmd, pycmd2argv
49 from IPython.utils.process import find_cmd, pycmd2argv
50 from IPython.utils.sysinfo import sys_info
50 from IPython.utils.sysinfo import sys_info
51
51
52 from IPython.testing import globalipapp
52 from IPython.testing import globalipapp
53 from IPython.testing.plugin.ipdoctest import IPythonDoctest
53 from IPython.testing.plugin.ipdoctest import IPythonDoctest
54 from IPython.external.decorators import KnownFailure
54 from IPython.external.decorators import KnownFailure
55
55
56 pjoin = path.join
56 pjoin = path.join
57
57
58
58
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60 # Globals
60 # Globals
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62
62
63
63
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65 # Warnings control
65 # Warnings control
66 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
67
67
68 # Twisted generates annoying warnings with Python 2.6, as will do other code
68 # Twisted generates annoying warnings with Python 2.6, as will do other code
69 # that imports 'sets' as of today
69 # that imports 'sets' as of today
70 warnings.filterwarnings('ignore', 'the sets module is deprecated',
70 warnings.filterwarnings('ignore', 'the sets module is deprecated',
71 DeprecationWarning )
71 DeprecationWarning )
72
72
73 # This one also comes from Twisted
73 # This one also comes from Twisted
74 warnings.filterwarnings('ignore', 'the sha module is deprecated',
74 warnings.filterwarnings('ignore', 'the sha module is deprecated',
75 DeprecationWarning)
75 DeprecationWarning)
76
76
77 # Wx on Fedora11 spits these out
77 # Wx on Fedora11 spits these out
78 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
78 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
79 UserWarning)
79 UserWarning)
80
80
81 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
82 # Logic for skipping doctests
82 # Logic for skipping doctests
83 #-----------------------------------------------------------------------------
83 #-----------------------------------------------------------------------------
84
84
85 def test_for(mod, min_version=None):
85 def test_for(mod, min_version=None):
86 """Test to see if mod is importable."""
86 """Test to see if mod is importable."""
87 try:
87 try:
88 __import__(mod)
88 __import__(mod)
89 except (ImportError, RuntimeError):
89 except (ImportError, RuntimeError):
90 # GTK reports Runtime error if it can't be initialized even if it's
90 # GTK reports Runtime error if it can't be initialized even if it's
91 # importable.
91 # importable.
92 return False
92 return False
93 else:
93 else:
94 if min_version:
94 if min_version:
95 return sys.modules[mod].__version__ >= min_version
95 return sys.modules[mod].__version__ >= min_version
96 else:
96 else:
97 return True
97 return True
98
98
99 # Global dict where we can store information on what we have and what we don't
99 # Global dict where we can store information on what we have and what we don't
100 # have available at test run time
100 # have available at test run time
101 have = {}
101 have = {}
102
102
103 have['curses'] = test_for('_curses')
103 have['curses'] = test_for('_curses')
104 have['matplotlib'] = test_for('matplotlib')
104 have['matplotlib'] = test_for('matplotlib')
105 have['pexpect'] = test_for('pexpect')
105 have['pexpect'] = test_for('pexpect')
106 have['pymongo'] = test_for('pymongo')
106 have['pymongo'] = test_for('pymongo')
107 have['wx'] = test_for('wx')
107 have['wx'] = test_for('wx')
108 have['wx.aui'] = test_for('wx.aui')
108 have['wx.aui'] = test_for('wx.aui')
109 if os.name == 'nt':
109 if os.name == 'nt':
110 have['zmq'] = test_for('zmq', '2.1.7')
110 have['zmq'] = test_for('zmq', '2.1.7')
111 else:
111 else:
112 have['zmq'] = test_for('zmq', '2.1.4')
112 have['zmq'] = test_for('zmq', '2.1.4')
113 have['qt'] = test_for('IPython.external.qt')
113 have['qt'] = test_for('IPython.external.qt')
114
114
115 #-----------------------------------------------------------------------------
115 #-----------------------------------------------------------------------------
116 # Functions and classes
116 # Functions and classes
117 #-----------------------------------------------------------------------------
117 #-----------------------------------------------------------------------------
118
118
119 def report():
119 def report():
120 """Return a string with a summary report of test-related variables."""
120 """Return a string with a summary report of test-related variables."""
121
121
122 out = [ sys_info(), '\n']
122 out = [ sys_info(), '\n']
123
123
124 avail = []
124 avail = []
125 not_avail = []
125 not_avail = []
126
126
127 for k, is_avail in have.items():
127 for k, is_avail in have.items():
128 if is_avail:
128 if is_avail:
129 avail.append(k)
129 avail.append(k)
130 else:
130 else:
131 not_avail.append(k)
131 not_avail.append(k)
132
132
133 if avail:
133 if avail:
134 out.append('\nTools and libraries available at test time:\n')
134 out.append('\nTools and libraries available at test time:\n')
135 avail.sort()
135 avail.sort()
136 out.append(' ' + ' '.join(avail)+'\n')
136 out.append(' ' + ' '.join(avail)+'\n')
137
137
138 if not_avail:
138 if not_avail:
139 out.append('\nTools and libraries NOT available at test time:\n')
139 out.append('\nTools and libraries NOT available at test time:\n')
140 not_avail.sort()
140 not_avail.sort()
141 out.append(' ' + ' '.join(not_avail)+'\n')
141 out.append(' ' + ' '.join(not_avail)+'\n')
142
142
143 return ''.join(out)
143 return ''.join(out)
144
144
145
145
146 def make_exclude():
146 def make_exclude():
147 """Make patterns of modules and packages to exclude from testing.
147 """Make patterns of modules and packages to exclude from testing.
148
148
149 For the IPythonDoctest plugin, we need to exclude certain patterns that
149 For the IPythonDoctest plugin, we need to exclude certain patterns that
150 cause testing problems. We should strive to minimize the number of
150 cause testing problems. We should strive to minimize the number of
151 skipped modules, since this means untested code.
151 skipped modules, since this means untested code.
152
152
153 These modules and packages will NOT get scanned by nose at all for tests.
153 These modules and packages will NOT get scanned by nose at all for tests.
154 """
154 """
155 # Simple utility to make IPython paths more readably, we need a lot of
155 # Simple utility to make IPython paths more readably, we need a lot of
156 # these below
156 # these below
157 ipjoin = lambda *paths: pjoin('IPython', *paths)
157 ipjoin = lambda *paths: pjoin('IPython', *paths)
158
158
159 exclusions = [ipjoin('external'),
159 exclusions = [ipjoin('external'),
160 pjoin('IPython_doctest_plugin'),
160 pjoin('IPython_doctest_plugin'),
161 ipjoin('quarantine'),
161 ipjoin('quarantine'),
162 ipjoin('deathrow'),
162 ipjoin('deathrow'),
163 ipjoin('testing', 'attic'),
163 ipjoin('testing', 'attic'),
164 # This guy is probably attic material
164 # This guy is probably attic material
165 ipjoin('testing', 'mkdoctests'),
165 ipjoin('testing', 'mkdoctests'),
166 # Testing inputhook will need a lot of thought, to figure out
166 # Testing inputhook will need a lot of thought, to figure out
167 # how to have tests that don't lock up with the gui event
167 # how to have tests that don't lock up with the gui event
168 # loops in the picture
168 # loops in the picture
169 ipjoin('lib', 'inputhook'),
169 ipjoin('lib', 'inputhook'),
170 # Config files aren't really importable stand-alone
170 # Config files aren't really importable stand-alone
171 ipjoin('config', 'default'),
171 ipjoin('config', 'default'),
172 ipjoin('config', 'profile'),
172 ipjoin('config', 'profile'),
173 ]
173 ]
174
174
175 if not have['wx']:
175 if not have['wx']:
176 exclusions.append(ipjoin('lib', 'inputhookwx'))
176 exclusions.append(ipjoin('lib', 'inputhookwx'))
177
177
178 # We do this unconditionally, so that the test suite doesn't import
178 # We do this unconditionally, so that the test suite doesn't import
179 # gtk, changing the default encoding and masking some unicode bugs.
179 # gtk, changing the default encoding and masking some unicode bugs.
180 exclusions.append(ipjoin('lib', 'inputhookgtk'))
180 exclusions.append(ipjoin('lib', 'inputhookgtk'))
181
181
182 # These have to be skipped on win32 because the use echo, rm, cd, etc.
182 # These have to be skipped on win32 because the use echo, rm, cd, etc.
183 # See ticket https://github.com/ipython/ipython/issues/87
183 # See ticket https://github.com/ipython/ipython/issues/87
184 if sys.platform == 'win32':
184 if sys.platform == 'win32':
185 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
185 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
186 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
186 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
187
187
188 if not have['pexpect']:
188 if not have['pexpect']:
189 exclusions.extend([ipjoin('scripts', 'irunner'),
189 exclusions.extend([ipjoin('scripts', 'irunner'),
190 ipjoin('lib', 'irunner'),
190 ipjoin('lib', 'irunner'),
191 ipjoin('lib', 'tests', 'test_irunner')])
191 ipjoin('lib', 'tests', 'test_irunner')])
192
192
193 if not have['zmq']:
193 if not have['zmq']:
194 exclusions.append(ipjoin('zmq'))
194 exclusions.append(ipjoin('zmq'))
195 exclusions.append(ipjoin('frontend', 'qt'))
195 exclusions.append(ipjoin('frontend', 'qt'))
196 exclusions.append(ipjoin('parallel'))
196 exclusions.append(ipjoin('parallel'))
197 elif not have['qt']:
197 elif not have['qt']:
198 exclusions.append(ipjoin('frontend', 'qt'))
198 exclusions.append(ipjoin('frontend', 'qt'))
199
199
200 if not have['pymongo']:
200 if not have['pymongo']:
201 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
201 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
202 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
202 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
203
203
204 if not have['matplotlib']:
204 if not have['matplotlib']:
205 exclusions.extend([ipjoin('lib', 'pylabtools'),
205 exclusions.extend([ipjoin('lib', 'pylabtools'),
206 ipjoin('lib', 'tests', 'test_pylabtools')])
206 ipjoin('lib', 'tests', 'test_pylabtools')])
207
207
208 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
208 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
209 if sys.platform == 'win32':
209 if sys.platform == 'win32':
210 exclusions = [s.replace('\\','\\\\') for s in exclusions]
210 exclusions = [s.replace('\\','\\\\') for s in exclusions]
211
211
212 return exclusions
212 return exclusions
213
213
214
214
215 class IPTester(object):
215 class IPTester(object):
216 """Call that calls iptest or trial in a subprocess.
216 """Call that calls iptest or trial in a subprocess.
217 """
217 """
218 #: string, name of test runner that will be called
218 #: string, name of test runner that will be called
219 runner = None
219 runner = None
220 #: list, parameters for test runner
220 #: list, parameters for test runner
221 params = None
221 params = None
222 #: list, arguments of system call to be made to call test runner
222 #: list, arguments of system call to be made to call test runner
223 call_args = None
223 call_args = None
224 #: list, process ids of subprocesses we start (for cleanup)
224 #: list, process ids of subprocesses we start (for cleanup)
225 pids = None
225 pids = None
226
226
227 def __init__(self, runner='iptest', params=None):
227 def __init__(self, runner='iptest', params=None):
228 """Create new test runner."""
228 """Create new test runner."""
229 p = os.path
229 p = os.path
230 if runner == 'iptest':
230 if runner == 'iptest':
231 iptest_app = get_ipython_module_path('IPython.testing.iptest')
231 iptest_app = get_ipython_module_path('IPython.testing.iptest')
232 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
232 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
233 else:
233 else:
234 raise Exception('Not a valid test runner: %s' % repr(runner))
234 raise Exception('Not a valid test runner: %s' % repr(runner))
235 if params is None:
235 if params is None:
236 params = []
236 params = []
237 if isinstance(params, str):
237 if isinstance(params, str):
238 params = [params]
238 params = [params]
239 self.params = params
239 self.params = params
240
240
241 # Assemble call
241 # Assemble call
242 self.call_args = self.runner+self.params
242 self.call_args = self.runner+self.params
243
243
244 # Store pids of anything we start to clean up on deletion, if possible
244 # Store pids of anything we start to clean up on deletion, if possible
245 # (on posix only, since win32 has no os.kill)
245 # (on posix only, since win32 has no os.kill)
246 self.pids = []
246 self.pids = []
247
247
248 if sys.platform == 'win32':
248 if sys.platform == 'win32':
249 def _run_cmd(self):
249 def _run_cmd(self):
250 # On Windows, use os.system instead of subprocess.call, because I
250 # On Windows, use os.system instead of subprocess.call, because I
251 # was having problems with subprocess and I just don't know enough
251 # was having problems with subprocess and I just don't know enough
252 # about win32 to debug this reliably. Os.system may be the 'old
252 # about win32 to debug this reliably. Os.system may be the 'old
253 # fashioned' way to do it, but it works just fine. If someone
253 # fashioned' way to do it, but it works just fine. If someone
254 # later can clean this up that's fine, as long as the tests run
254 # later can clean this up that's fine, as long as the tests run
255 # reliably in win32.
255 # reliably in win32.
256 # What types of problems are you having. They may be related to
256 # What types of problems are you having. They may be related to
257 # running Python in unboffered mode. BG.
257 # running Python in unboffered mode. BG.
258 return os.system(' '.join(self.call_args))
258 return os.system(' '.join(self.call_args))
259 else:
259 else:
260 def _run_cmd(self):
260 def _run_cmd(self):
261 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
261 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
262 subp = subprocess.Popen(self.call_args)
262 subp = subprocess.Popen(self.call_args)
263 self.pids.append(subp.pid)
263 self.pids.append(subp.pid)
264 # If this fails, the pid will be left in self.pids and cleaned up
264 # If this fails, the pid will be left in self.pids and cleaned up
265 # later, but if the wait call succeeds, then we can clear the
265 # later, but if the wait call succeeds, then we can clear the
266 # stored pid.
266 # stored pid.
267 retcode = subp.wait()
267 retcode = subp.wait()
268 self.pids.pop()
268 self.pids.pop()
269 return retcode
269 return retcode
270
270
271 def run(self):
271 def run(self):
272 """Run the stored commands"""
272 """Run the stored commands"""
273 try:
273 try:
274 return self._run_cmd()
274 return self._run_cmd()
275 except:
275 except:
276 import traceback
276 import traceback
277 traceback.print_exc()
277 traceback.print_exc()
278 return 1 # signal failure
278 return 1 # signal failure
279
279
280 def __del__(self):
280 def __del__(self):
281 """Cleanup on exit by killing any leftover processes."""
281 """Cleanup on exit by killing any leftover processes."""
282
282
283 if not hasattr(os, 'kill'):
283 if not hasattr(os, 'kill'):
284 return
284 return
285
285
286 for pid in self.pids:
286 for pid in self.pids:
287 try:
287 try:
288 print 'Cleaning stale PID:', pid
288 print 'Cleaning stale PID:', pid
289 os.kill(pid, signal.SIGKILL)
289 os.kill(pid, signal.SIGKILL)
290 except OSError:
290 except OSError:
291 # This is just a best effort, if we fail or the process was
291 # This is just a best effort, if we fail or the process was
292 # really gone, ignore it.
292 # really gone, ignore it.
293 pass
293 pass
294
294
295
295
296 def make_runners():
296 def make_runners():
297 """Define the top-level packages that need to be tested.
297 """Define the top-level packages that need to be tested.
298 """
298 """
299
299
300 # Packages to be tested via nose, that only depend on the stdlib
300 # Packages to be tested via nose, that only depend on the stdlib
301 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
301 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
302 'scripts', 'testing', 'utils' ]
302 'scripts', 'testing', 'utils', 'nbformat' ]
303
303
304 if have['zmq']:
304 if have['zmq']:
305 nose_pkg_names.append('parallel')
305 nose_pkg_names.append('parallel')
306
306
307 # For debugging this code, only load quick stuff
307 # For debugging this code, only load quick stuff
308 #nose_pkg_names = ['core', 'extensions'] # dbg
308 #nose_pkg_names = ['core', 'extensions'] # dbg
309
309
310 # Make fully qualified package names prepending 'IPython.' to our name lists
310 # Make fully qualified package names prepending 'IPython.' to our name lists
311 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
311 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
312
312
313 # Make runners
313 # Make runners
314 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
314 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
315
315
316 return runners
316 return runners
317
317
318
318
319 def run_iptest():
319 def run_iptest():
320 """Run the IPython test suite using nose.
320 """Run the IPython test suite using nose.
321
321
322 This function is called when this script is **not** called with the form
322 This function is called when this script is **not** called with the form
323 `iptest all`. It simply calls nose with appropriate command line flags
323 `iptest all`. It simply calls nose with appropriate command line flags
324 and accepts all of the standard nose arguments.
324 and accepts all of the standard nose arguments.
325 """
325 """
326
326
327 warnings.filterwarnings('ignore',
327 warnings.filterwarnings('ignore',
328 'This will be removed soon. Use IPython.testing.util instead')
328 'This will be removed soon. Use IPython.testing.util instead')
329
329
330 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
330 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
331
331
332 # Loading ipdoctest causes problems with Twisted, but
332 # Loading ipdoctest causes problems with Twisted, but
333 # our test suite runner now separates things and runs
333 # our test suite runner now separates things and runs
334 # all Twisted tests with trial.
334 # all Twisted tests with trial.
335 '--with-ipdoctest',
335 '--with-ipdoctest',
336 '--ipdoctest-tests','--ipdoctest-extension=txt',
336 '--ipdoctest-tests','--ipdoctest-extension=txt',
337
337
338 # We add --exe because of setuptools' imbecility (it
338 # We add --exe because of setuptools' imbecility (it
339 # blindly does chmod +x on ALL files). Nose does the
339 # blindly does chmod +x on ALL files). Nose does the
340 # right thing and it tries to avoid executables,
340 # right thing and it tries to avoid executables,
341 # setuptools unfortunately forces our hand here. This
341 # setuptools unfortunately forces our hand here. This
342 # has been discussed on the distutils list and the
342 # has been discussed on the distutils list and the
343 # setuptools devs refuse to fix this problem!
343 # setuptools devs refuse to fix this problem!
344 '--exe',
344 '--exe',
345 ]
345 ]
346
346
347 if nose.__version__ >= '0.11':
347 if nose.__version__ >= '0.11':
348 # I don't fully understand why we need this one, but depending on what
348 # I don't fully understand why we need this one, but depending on what
349 # directory the test suite is run from, if we don't give it, 0 tests
349 # directory the test suite is run from, if we don't give it, 0 tests
350 # get run. Specifically, if the test suite is run from the source dir
350 # get run. Specifically, if the test suite is run from the source dir
351 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
351 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
352 # even if the same call done in this directory works fine). It appears
352 # even if the same call done in this directory works fine). It appears
353 # that if the requested package is in the current dir, nose bails early
353 # that if the requested package is in the current dir, nose bails early
354 # by default. Since it's otherwise harmless, leave it in by default
354 # by default. Since it's otherwise harmless, leave it in by default
355 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
355 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
356 argv.append('--traverse-namespace')
356 argv.append('--traverse-namespace')
357
357
358 # Construct list of plugins, omitting the existing doctest plugin, which
358 # Construct list of plugins, omitting the existing doctest plugin, which
359 # ours replaces (and extends).
359 # ours replaces (and extends).
360 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
360 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
361 for p in nose.plugins.builtin.plugins:
361 for p in nose.plugins.builtin.plugins:
362 plug = p()
362 plug = p()
363 if plug.name == 'doctest':
363 if plug.name == 'doctest':
364 continue
364 continue
365 plugins.append(plug)
365 plugins.append(plug)
366
366
367 # We need a global ipython running in this process
367 # We need a global ipython running in this process
368 globalipapp.start_ipython()
368 globalipapp.start_ipython()
369 # Now nose can run
369 # Now nose can run
370 TestProgram(argv=argv, plugins=plugins)
370 TestProgram(argv=argv, plugins=plugins)
371
371
372
372
373 def run_iptestall():
373 def run_iptestall():
374 """Run the entire IPython test suite by calling nose and trial.
374 """Run the entire IPython test suite by calling nose and trial.
375
375
376 This function constructs :class:`IPTester` instances for all IPython
376 This function constructs :class:`IPTester` instances for all IPython
377 modules and package and then runs each of them. This causes the modules
377 modules and package and then runs each of them. This causes the modules
378 and packages of IPython to be tested each in their own subprocess using
378 and packages of IPython to be tested each in their own subprocess using
379 nose or twisted.trial appropriately.
379 nose or twisted.trial appropriately.
380 """
380 """
381
381
382 runners = make_runners()
382 runners = make_runners()
383
383
384 # Run the test runners in a temporary dir so we can nuke it when finished
384 # Run the test runners in a temporary dir so we can nuke it when finished
385 # to clean up any junk files left over by accident. This also makes it
385 # to clean up any junk files left over by accident. This also makes it
386 # robust against being run in non-writeable directories by mistake, as the
386 # robust against being run in non-writeable directories by mistake, as the
387 # temp dir will always be user-writeable.
387 # temp dir will always be user-writeable.
388 curdir = os.getcwdu()
388 curdir = os.getcwdu()
389 testdir = tempfile.gettempdir()
389 testdir = tempfile.gettempdir()
390 os.chdir(testdir)
390 os.chdir(testdir)
391
391
392 # Run all test runners, tracking execution time
392 # Run all test runners, tracking execution time
393 failed = []
393 failed = []
394 t_start = time.time()
394 t_start = time.time()
395 try:
395 try:
396 for (name, runner) in runners:
396 for (name, runner) in runners:
397 print '*'*70
397 print '*'*70
398 print 'IPython test group:',name
398 print 'IPython test group:',name
399 res = runner.run()
399 res = runner.run()
400 if res:
400 if res:
401 failed.append( (name, runner) )
401 failed.append( (name, runner) )
402 finally:
402 finally:
403 os.chdir(curdir)
403 os.chdir(curdir)
404 t_end = time.time()
404 t_end = time.time()
405 t_tests = t_end - t_start
405 t_tests = t_end - t_start
406 nrunners = len(runners)
406 nrunners = len(runners)
407 nfail = len(failed)
407 nfail = len(failed)
408 # summarize results
408 # summarize results
409 print
409 print
410 print '*'*70
410 print '*'*70
411 print 'Test suite completed for system with the following information:'
411 print 'Test suite completed for system with the following information:'
412 print report()
412 print report()
413 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
413 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
414 print
414 print
415 print 'Status:'
415 print 'Status:'
416 if not failed:
416 if not failed:
417 print 'OK'
417 print 'OK'
418 else:
418 else:
419 # If anything went wrong, point out what command to rerun manually to
419 # If anything went wrong, point out what command to rerun manually to
420 # see the actual errors and individual summary
420 # see the actual errors and individual summary
421 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
421 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
422 for name, failed_runner in failed:
422 for name, failed_runner in failed:
423 print '-'*40
423 print '-'*40
424 print 'Runner failed:',name
424 print 'Runner failed:',name
425 print 'You may wish to rerun this one individually, with:'
425 print 'You may wish to rerun this one individually, with:'
426 print ' '.join(failed_runner.call_args)
426 print ' '.join(failed_runner.call_args)
427 print
427 print
428 # Ensure that our exit code indicates failure
428 # Ensure that our exit code indicates failure
429 sys.exit(1)
429 sys.exit(1)
430
430
431
431
432 def main():
432 def main():
433 for arg in sys.argv[1:]:
433 for arg in sys.argv[1:]:
434 if arg.startswith('IPython'):
434 if arg.startswith('IPython'):
435 # This is in-process
435 # This is in-process
436 run_iptest()
436 run_iptest()
437 else:
437 else:
438 # This starts subprocesses
438 # This starts subprocesses
439 run_iptestall()
439 run_iptestall()
440
440
441
441
442 if __name__ == '__main__':
442 if __name__ == '__main__':
443 main()
443 main()
@@ -1,401 +1,402 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 This module defines the things that are used in setup.py for building IPython
3 This module defines the things that are used in setup.py for building IPython
4
4
5 This includes:
5 This includes:
6
6
7 * The basic arguments to setup
7 * The basic arguments to setup
8 * Functions for finding things like packages, package data, etc.
8 * Functions for finding things like packages, package data, etc.
9 * A function for checking dependencies.
9 * A function for checking dependencies.
10 """
10 """
11 from __future__ import print_function
11 from __future__ import print_function
12
12
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14 # Copyright (C) 2008 The IPython Development Team
14 # Copyright (C) 2008 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19
19
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-------------------------------------------------------------------------------
22 #-------------------------------------------------------------------------------
23 import os
23 import os
24 import sys
24 import sys
25
25
26 from ConfigParser import ConfigParser
26 from ConfigParser import ConfigParser
27 from distutils.command.build_py import build_py
27 from distutils.command.build_py import build_py
28 from glob import glob
28 from glob import glob
29
29
30 from setupext import install_data_ext
30 from setupext import install_data_ext
31
31
32 #-------------------------------------------------------------------------------
32 #-------------------------------------------------------------------------------
33 # Useful globals and utility functions
33 # Useful globals and utility functions
34 #-------------------------------------------------------------------------------
34 #-------------------------------------------------------------------------------
35
35
36 # A few handy globals
36 # A few handy globals
37 isfile = os.path.isfile
37 isfile = os.path.isfile
38 pjoin = os.path.join
38 pjoin = os.path.join
39
39
40 def oscmd(s):
40 def oscmd(s):
41 print(">", s)
41 print(">", s)
42 os.system(s)
42 os.system(s)
43
43
44 # A little utility we'll need below, since glob() does NOT allow you to do
44 # A little utility we'll need below, since glob() does NOT allow you to do
45 # exclusion on multiple endings!
45 # exclusion on multiple endings!
46 def file_doesnt_endwith(test,endings):
46 def file_doesnt_endwith(test,endings):
47 """Return true if test is a file and its name does NOT end with any
47 """Return true if test is a file and its name does NOT end with any
48 of the strings listed in endings."""
48 of the strings listed in endings."""
49 if not isfile(test):
49 if not isfile(test):
50 return False
50 return False
51 for e in endings:
51 for e in endings:
52 if test.endswith(e):
52 if test.endswith(e):
53 return False
53 return False
54 return True
54 return True
55
55
56 #---------------------------------------------------------------------------
56 #---------------------------------------------------------------------------
57 # Basic project information
57 # Basic project information
58 #---------------------------------------------------------------------------
58 #---------------------------------------------------------------------------
59
59
60 # release.py contains version, authors, license, url, keywords, etc.
60 # release.py contains version, authors, license, url, keywords, etc.
61 execfile(pjoin('IPython','core','release.py'))
61 execfile(pjoin('IPython','core','release.py'))
62
62
63 # Create a dict with the basic information
63 # Create a dict with the basic information
64 # This dict is eventually passed to setup after additional keys are added.
64 # This dict is eventually passed to setup after additional keys are added.
65 setup_args = dict(
65 setup_args = dict(
66 name = name,
66 name = name,
67 version = version,
67 version = version,
68 description = description,
68 description = description,
69 long_description = long_description,
69 long_description = long_description,
70 author = author,
70 author = author,
71 author_email = author_email,
71 author_email = author_email,
72 url = url,
72 url = url,
73 download_url = download_url,
73 download_url = download_url,
74 license = license,
74 license = license,
75 platforms = platforms,
75 platforms = platforms,
76 keywords = keywords,
76 keywords = keywords,
77 cmdclass = {'install_data': install_data_ext},
77 cmdclass = {'install_data': install_data_ext},
78 )
78 )
79
79
80
80
81 #---------------------------------------------------------------------------
81 #---------------------------------------------------------------------------
82 # Find packages
82 # Find packages
83 #---------------------------------------------------------------------------
83 #---------------------------------------------------------------------------
84
84
85 def add_package(packages,pname,config=False,tests=False,scripts=False,
85 def add_package(packages,pname,config=False,tests=False,scripts=False,
86 others=None):
86 others=None):
87 """
87 """
88 Add a package to the list of packages, including certain subpackages.
88 Add a package to the list of packages, including certain subpackages.
89 """
89 """
90 packages.append('.'.join(['IPython',pname]))
90 packages.append('.'.join(['IPython',pname]))
91 if config:
91 if config:
92 packages.append('.'.join(['IPython',pname,'config']))
92 packages.append('.'.join(['IPython',pname,'config']))
93 if tests:
93 if tests:
94 packages.append('.'.join(['IPython',pname,'tests']))
94 packages.append('.'.join(['IPython',pname,'tests']))
95 if scripts:
95 if scripts:
96 packages.append('.'.join(['IPython',pname,'scripts']))
96 packages.append('.'.join(['IPython',pname,'scripts']))
97 if others is not None:
97 if others is not None:
98 for o in others:
98 for o in others:
99 packages.append('.'.join(['IPython',pname,o]))
99 packages.append('.'.join(['IPython',pname,o]))
100
100
101 def find_packages():
101 def find_packages():
102 """
102 """
103 Find all of IPython's packages.
103 Find all of IPython's packages.
104 """
104 """
105 packages = ['IPython']
105 packages = ['IPython']
106 add_package(packages, 'config', tests=True, others=['profile'])
106 add_package(packages, 'config', tests=True, others=['profile'])
107 add_package(packages, 'core', tests=True)
107 add_package(packages, 'core', tests=True)
108 add_package(packages, 'extensions')
108 add_package(packages, 'extensions')
109 add_package(packages, 'external')
109 add_package(packages, 'external')
110 add_package(packages, 'external.argparse')
110 add_package(packages, 'external.argparse')
111 add_package(packages, 'external.decorator')
111 add_package(packages, 'external.decorator')
112 add_package(packages, 'external.decorators')
112 add_package(packages, 'external.decorators')
113 add_package(packages, 'external.guid')
113 add_package(packages, 'external.guid')
114 add_package(packages, 'external.Itpl')
114 add_package(packages, 'external.Itpl')
115 add_package(packages, 'external.mglob')
115 add_package(packages, 'external.mglob')
116 add_package(packages, 'external.path')
116 add_package(packages, 'external.path')
117 add_package(packages, 'external.pexpect')
117 add_package(packages, 'external.pexpect')
118 add_package(packages, 'external.pyparsing')
118 add_package(packages, 'external.pyparsing')
119 add_package(packages, 'external.simplegeneric')
119 add_package(packages, 'external.simplegeneric')
120 add_package(packages, 'external.ssh')
120 add_package(packages, 'external.ssh')
121 add_package(packages, 'kernel')
121 add_package(packages, 'kernel')
122 add_package(packages, 'frontend')
122 add_package(packages, 'frontend')
123 add_package(packages, 'frontend.html')
123 add_package(packages, 'frontend.html')
124 add_package(packages, 'frontend.html.notebook')
124 add_package(packages, 'frontend.html.notebook')
125 add_package(packages, 'frontend.qt')
125 add_package(packages, 'frontend.qt')
126 add_package(packages, 'frontend.qt.console', tests=True)
126 add_package(packages, 'frontend.qt.console', tests=True)
127 add_package(packages, 'frontend.terminal', tests=True)
127 add_package(packages, 'frontend.terminal', tests=True)
128 add_package(packages, 'lib', tests=True)
128 add_package(packages, 'lib', tests=True)
129 add_package(packages, 'nbformat', tests=True)
129 add_package(packages, 'parallel', tests=True, scripts=True,
130 add_package(packages, 'parallel', tests=True, scripts=True,
130 others=['apps','engine','client','controller'])
131 others=['apps','engine','client','controller'])
131 add_package(packages, 'quarantine', tests=True)
132 add_package(packages, 'quarantine', tests=True)
132 add_package(packages, 'scripts')
133 add_package(packages, 'scripts')
133 add_package(packages, 'testing', tests=True)
134 add_package(packages, 'testing', tests=True)
134 add_package(packages, 'testing.plugin', tests=False)
135 add_package(packages, 'testing.plugin', tests=False)
135 add_package(packages, 'utils', tests=True)
136 add_package(packages, 'utils', tests=True)
136 add_package(packages, 'zmq')
137 add_package(packages, 'zmq')
137 add_package(packages, 'zmq.pylab')
138 add_package(packages, 'zmq.pylab')
138 add_package(packages, 'zmq.gui')
139 add_package(packages, 'zmq.gui')
139 return packages
140 return packages
140
141
141 #---------------------------------------------------------------------------
142 #---------------------------------------------------------------------------
142 # Find package data
143 # Find package data
143 #---------------------------------------------------------------------------
144 #---------------------------------------------------------------------------
144
145
145 def find_package_data():
146 def find_package_data():
146 """
147 """
147 Find IPython's package_data.
148 Find IPython's package_data.
148 """
149 """
149 # This is not enough for these things to appear in an sdist.
150 # This is not enough for these things to appear in an sdist.
150 # We need to muck with the MANIFEST to get this to work
151 # We need to muck with the MANIFEST to get this to work
151
152
152 # walk notebook resources:
153 # walk notebook resources:
153 cwd = os.getcwd()
154 cwd = os.getcwd()
154 os.chdir(os.path.join('IPython', 'frontend', 'html', 'notebook'))
155 os.chdir(os.path.join('IPython', 'frontend', 'html', 'notebook'))
155 static_walk = list(os.walk('static'))
156 static_walk = list(os.walk('static'))
156 os.chdir(cwd)
157 os.chdir(cwd)
157 static_data = []
158 static_data = []
158 for parent, dirs, files in static_walk:
159 for parent, dirs, files in static_walk:
159 for f in files:
160 for f in files:
160 static_data.append(os.path.join(parent, f))
161 static_data.append(os.path.join(parent, f))
161
162
162 package_data = {
163 package_data = {
163 'IPython.config.profile' : ['README', '*/*.py'],
164 'IPython.config.profile' : ['README', '*/*.py'],
164 'IPython.testing' : ['*.txt'],
165 'IPython.testing' : ['*.txt'],
165 'IPython.frontend.html.notebook' : ['templates/*']+static_data
166 'IPython.frontend.html.notebook' : ['templates/*']+static_data
166 }
167 }
167 return package_data
168 return package_data
168
169
169
170
170 #---------------------------------------------------------------------------
171 #---------------------------------------------------------------------------
171 # Find data files
172 # Find data files
172 #---------------------------------------------------------------------------
173 #---------------------------------------------------------------------------
173
174
174 def make_dir_struct(tag,base,out_base):
175 def make_dir_struct(tag,base,out_base):
175 """Make the directory structure of all files below a starting dir.
176 """Make the directory structure of all files below a starting dir.
176
177
177 This is just a convenience routine to help build a nested directory
178 This is just a convenience routine to help build a nested directory
178 hierarchy because distutils is too stupid to do this by itself.
179 hierarchy because distutils is too stupid to do this by itself.
179
180
180 XXX - this needs a proper docstring!
181 XXX - this needs a proper docstring!
181 """
182 """
182
183
183 # we'll use these a lot below
184 # we'll use these a lot below
184 lbase = len(base)
185 lbase = len(base)
185 pathsep = os.path.sep
186 pathsep = os.path.sep
186 lpathsep = len(pathsep)
187 lpathsep = len(pathsep)
187
188
188 out = []
189 out = []
189 for (dirpath,dirnames,filenames) in os.walk(base):
190 for (dirpath,dirnames,filenames) in os.walk(base):
190 # we need to strip out the dirpath from the base to map it to the
191 # we need to strip out the dirpath from the base to map it to the
191 # output (installation) path. This requires possibly stripping the
192 # output (installation) path. This requires possibly stripping the
192 # path separator, because otherwise pjoin will not work correctly
193 # path separator, because otherwise pjoin will not work correctly
193 # (pjoin('foo/','/bar') returns '/bar').
194 # (pjoin('foo/','/bar') returns '/bar').
194
195
195 dp_eff = dirpath[lbase:]
196 dp_eff = dirpath[lbase:]
196 if dp_eff.startswith(pathsep):
197 if dp_eff.startswith(pathsep):
197 dp_eff = dp_eff[lpathsep:]
198 dp_eff = dp_eff[lpathsep:]
198 # The output path must be anchored at the out_base marker
199 # The output path must be anchored at the out_base marker
199 out_path = pjoin(out_base,dp_eff)
200 out_path = pjoin(out_base,dp_eff)
200 # Now we can generate the final filenames. Since os.walk only produces
201 # Now we can generate the final filenames. Since os.walk only produces
201 # filenames, we must join back with the dirpath to get full valid file
202 # filenames, we must join back with the dirpath to get full valid file
202 # paths:
203 # paths:
203 pfiles = [pjoin(dirpath,f) for f in filenames]
204 pfiles = [pjoin(dirpath,f) for f in filenames]
204 # Finally, generate the entry we need, which is a pari of (output
205 # Finally, generate the entry we need, which is a pari of (output
205 # path, files) for use as a data_files parameter in install_data.
206 # path, files) for use as a data_files parameter in install_data.
206 out.append((out_path, pfiles))
207 out.append((out_path, pfiles))
207
208
208 return out
209 return out
209
210
210
211
211 def find_data_files():
212 def find_data_files():
212 """
213 """
213 Find IPython's data_files.
214 Find IPython's data_files.
214
215
215 Most of these are docs.
216 Most of these are docs.
216 """
217 """
217
218
218 docdirbase = pjoin('share', 'doc', 'ipython')
219 docdirbase = pjoin('share', 'doc', 'ipython')
219 manpagebase = pjoin('share', 'man', 'man1')
220 manpagebase = pjoin('share', 'man', 'man1')
220
221
221 # Simple file lists can be made by hand
222 # Simple file lists can be made by hand
222 manpages = filter(isfile, glob(pjoin('docs','man','*.1.gz')))
223 manpages = filter(isfile, glob(pjoin('docs','man','*.1.gz')))
223 igridhelpfiles = filter(isfile,
224 igridhelpfiles = filter(isfile,
224 glob(pjoin('IPython','extensions','igrid_help.*')))
225 glob(pjoin('IPython','extensions','igrid_help.*')))
225
226
226 # For nested structures, use the utility above
227 # For nested structures, use the utility above
227 example_files = make_dir_struct(
228 example_files = make_dir_struct(
228 'data',
229 'data',
229 pjoin('docs','examples'),
230 pjoin('docs','examples'),
230 pjoin(docdirbase,'examples')
231 pjoin(docdirbase,'examples')
231 )
232 )
232 manual_files = make_dir_struct(
233 manual_files = make_dir_struct(
233 'data',
234 'data',
234 pjoin('docs','dist'),
235 pjoin('docs','dist'),
235 pjoin(docdirbase,'manual')
236 pjoin(docdirbase,'manual')
236 )
237 )
237
238
238 # And assemble the entire output list
239 # And assemble the entire output list
239 data_files = [ (manpagebase, manpages),
240 data_files = [ (manpagebase, manpages),
240 (pjoin(docdirbase, 'extensions'), igridhelpfiles),
241 (pjoin(docdirbase, 'extensions'), igridhelpfiles),
241 ] + manual_files + example_files
242 ] + manual_files + example_files
242
243
243 return data_files
244 return data_files
244
245
245
246
246 def make_man_update_target(manpage):
247 def make_man_update_target(manpage):
247 """Return a target_update-compliant tuple for the given manpage.
248 """Return a target_update-compliant tuple for the given manpage.
248
249
249 Parameters
250 Parameters
250 ----------
251 ----------
251 manpage : string
252 manpage : string
252 Name of the manpage, must include the section number (trailing number).
253 Name of the manpage, must include the section number (trailing number).
253
254
254 Example
255 Example
255 -------
256 -------
256
257
257 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
258 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
258 ('docs/man/ipython.1.gz',
259 ('docs/man/ipython.1.gz',
259 ['docs/man/ipython.1'],
260 ['docs/man/ipython.1'],
260 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
261 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
261 """
262 """
262 man_dir = pjoin('docs', 'man')
263 man_dir = pjoin('docs', 'man')
263 manpage_gz = manpage + '.gz'
264 manpage_gz = manpage + '.gz'
264 manpath = pjoin(man_dir, manpage)
265 manpath = pjoin(man_dir, manpage)
265 manpath_gz = pjoin(man_dir, manpage_gz)
266 manpath_gz = pjoin(man_dir, manpage_gz)
266 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
267 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
267 locals() )
268 locals() )
268 return (manpath_gz, [manpath], gz_cmd)
269 return (manpath_gz, [manpath], gz_cmd)
269
270
270 #---------------------------------------------------------------------------
271 #---------------------------------------------------------------------------
271 # Find scripts
272 # Find scripts
272 #---------------------------------------------------------------------------
273 #---------------------------------------------------------------------------
273
274
274 def find_scripts(entry_points=False):
275 def find_scripts(entry_points=False):
275 """Find IPython's scripts.
276 """Find IPython's scripts.
276
277
277 if entry_points is True:
278 if entry_points is True:
278 return setuptools entry_point-style definitions
279 return setuptools entry_point-style definitions
279 else:
280 else:
280 return file paths of plain scripts [default]
281 return file paths of plain scripts [default]
281 """
282 """
282 if entry_points:
283 if entry_points:
283 console_scripts = [
284 console_scripts = [
284 'ipython = IPython.frontend.terminal.ipapp:launch_new_instance',
285 'ipython = IPython.frontend.terminal.ipapp:launch_new_instance',
285 'ipython-notebook = IPython.frontend.html.notebook.notebookapp:launch_new_instance',
286 'ipython-notebook = IPython.frontend.html.notebook.notebookapp:launch_new_instance',
286 'pycolor = IPython.utils.PyColorize:main',
287 'pycolor = IPython.utils.PyColorize:main',
287 'ipcontroller = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
288 'ipcontroller = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
288 'ipengine = IPython.parallel.apps.ipengineapp:launch_new_instance',
289 'ipengine = IPython.parallel.apps.ipengineapp:launch_new_instance',
289 'iplogger = IPython.parallel.apps.iploggerapp:launch_new_instance',
290 'iplogger = IPython.parallel.apps.iploggerapp:launch_new_instance',
290 'ipcluster = IPython.parallel.apps.ipclusterapp:launch_new_instance',
291 'ipcluster = IPython.parallel.apps.ipclusterapp:launch_new_instance',
291 'iptest = IPython.testing.iptest:main',
292 'iptest = IPython.testing.iptest:main',
292 'irunner = IPython.lib.irunner:main'
293 'irunner = IPython.lib.irunner:main'
293 ]
294 ]
294 gui_scripts = [
295 gui_scripts = [
295 'ipython-qtconsole = IPython.frontend.qt.console.qtconsoleapp:main',
296 'ipython-qtconsole = IPython.frontend.qt.console.qtconsoleapp:main',
296 ]
297 ]
297 scripts = dict(console_scripts=console_scripts, gui_scripts=gui_scripts)
298 scripts = dict(console_scripts=console_scripts, gui_scripts=gui_scripts)
298 else:
299 else:
299 parallel_scripts = pjoin('IPython','parallel','scripts')
300 parallel_scripts = pjoin('IPython','parallel','scripts')
300 main_scripts = pjoin('IPython','scripts')
301 main_scripts = pjoin('IPython','scripts')
301 scripts = [
302 scripts = [
302 pjoin(parallel_scripts, 'ipengine'),
303 pjoin(parallel_scripts, 'ipengine'),
303 pjoin(parallel_scripts, 'ipcontroller'),
304 pjoin(parallel_scripts, 'ipcontroller'),
304 pjoin(parallel_scripts, 'ipcluster'),
305 pjoin(parallel_scripts, 'ipcluster'),
305 pjoin(parallel_scripts, 'iplogger'),
306 pjoin(parallel_scripts, 'iplogger'),
306 pjoin(main_scripts, 'ipython'),
307 pjoin(main_scripts, 'ipython'),
307 pjoin(main_scripts, 'ipython-notebook'),
308 pjoin(main_scripts, 'ipython-notebook'),
308 pjoin(main_scripts, 'pycolor'),
309 pjoin(main_scripts, 'pycolor'),
309 pjoin(main_scripts, 'irunner'),
310 pjoin(main_scripts, 'irunner'),
310 pjoin(main_scripts, 'iptest')
311 pjoin(main_scripts, 'iptest')
311 ]
312 ]
312 return scripts
313 return scripts
313
314
314 #---------------------------------------------------------------------------
315 #---------------------------------------------------------------------------
315 # Verify all dependencies
316 # Verify all dependencies
316 #---------------------------------------------------------------------------
317 #---------------------------------------------------------------------------
317
318
318 def check_for_dependencies():
319 def check_for_dependencies():
319 """Check for IPython's dependencies.
320 """Check for IPython's dependencies.
320
321
321 This function should NOT be called if running under setuptools!
322 This function should NOT be called if running under setuptools!
322 """
323 """
323 from setupext.setupext import (
324 from setupext.setupext import (
324 print_line, print_raw, print_status,
325 print_line, print_raw, print_status,
325 check_for_sphinx, check_for_pygments,
326 check_for_sphinx, check_for_pygments,
326 check_for_nose, check_for_pexpect,
327 check_for_nose, check_for_pexpect,
327 check_for_pyzmq, check_for_readline
328 check_for_pyzmq, check_for_readline
328 )
329 )
329 print_line()
330 print_line()
330 print_raw("BUILDING IPYTHON")
331 print_raw("BUILDING IPYTHON")
331 print_status('python', sys.version)
332 print_status('python', sys.version)
332 print_status('platform', sys.platform)
333 print_status('platform', sys.platform)
333 if sys.platform == 'win32':
334 if sys.platform == 'win32':
334 print_status('Windows version', sys.getwindowsversion())
335 print_status('Windows version', sys.getwindowsversion())
335
336
336 print_raw("")
337 print_raw("")
337 print_raw("OPTIONAL DEPENDENCIES")
338 print_raw("OPTIONAL DEPENDENCIES")
338
339
339 check_for_sphinx()
340 check_for_sphinx()
340 check_for_pygments()
341 check_for_pygments()
341 check_for_nose()
342 check_for_nose()
342 check_for_pexpect()
343 check_for_pexpect()
343 check_for_pyzmq()
344 check_for_pyzmq()
344 check_for_readline()
345 check_for_readline()
345
346
346 def record_commit_info(pkg_dir, build_cmd=build_py):
347 def record_commit_info(pkg_dir, build_cmd=build_py):
347 """ Return extended build command class for recording commit
348 """ Return extended build command class for recording commit
348
349
349 The extended command tries to run git to find the current commit, getting
350 The extended command tries to run git to find the current commit, getting
350 the empty string if it fails. It then writes the commit hash into a file
351 the empty string if it fails. It then writes the commit hash into a file
351 in the `pkg_dir` path, named ``.git_commit_info.ini``.
352 in the `pkg_dir` path, named ``.git_commit_info.ini``.
352
353
353 In due course this information can be used by the package after it is
354 In due course this information can be used by the package after it is
354 installed, to tell you what commit it was installed from if known.
355 installed, to tell you what commit it was installed from if known.
355
356
356 To make use of this system, you need a package with a .git_commit_info.ini
357 To make use of this system, you need a package with a .git_commit_info.ini
357 file - e.g. ``myproject/.git_commit_info.ini`` - that might well look like
358 file - e.g. ``myproject/.git_commit_info.ini`` - that might well look like
358 this::
359 this::
359
360
360 # This is an ini file that may contain information about the code state
361 # This is an ini file that may contain information about the code state
361 [commit hash]
362 [commit hash]
362 # The line below may contain a valid hash if it has been substituted
363 # The line below may contain a valid hash if it has been substituted
363 # during 'git archive'
364 # during 'git archive'
364 archive_subst_hash=$Format:%h$
365 archive_subst_hash=$Format:%h$
365 # This line may be modified by the install process
366 # This line may be modified by the install process
366 install_hash=
367 install_hash=
367
368
368 The .git_commit_info file above is also designed to be used with git
369 The .git_commit_info file above is also designed to be used with git
369 substitution - so you probably also want a ``.gitattributes`` file in the
370 substitution - so you probably also want a ``.gitattributes`` file in the
370 root directory of your working tree that contains something like this::
371 root directory of your working tree that contains something like this::
371
372
372 myproject/.git_commit_info.ini export-subst
373 myproject/.git_commit_info.ini export-subst
373
374
374 That will cause the ``.git_commit_info.ini`` file to get filled in by ``git
375 That will cause the ``.git_commit_info.ini`` file to get filled in by ``git
375 archive`` - useful in case someone makes such an archive - for example with
376 archive`` - useful in case someone makes such an archive - for example with
376 via the github 'download source' button.
377 via the github 'download source' button.
377
378
378 Although all the above will work as is, you might consider having something
379 Although all the above will work as is, you might consider having something
379 like a ``get_info()`` function in your package to display the commit
380 like a ``get_info()`` function in your package to display the commit
380 information at the terminal. See the ``pkg_info.py`` module in the nipy
381 information at the terminal. See the ``pkg_info.py`` module in the nipy
381 package for an example.
382 package for an example.
382 """
383 """
383 class MyBuildPy(build_cmd):
384 class MyBuildPy(build_cmd):
384 ''' Subclass to write commit data into installation tree '''
385 ''' Subclass to write commit data into installation tree '''
385 def run(self):
386 def run(self):
386 build_py.run(self)
387 build_py.run(self)
387 import subprocess
388 import subprocess
388 proc = subprocess.Popen('git rev-parse --short HEAD',
389 proc = subprocess.Popen('git rev-parse --short HEAD',
389 stdout=subprocess.PIPE,
390 stdout=subprocess.PIPE,
390 stderr=subprocess.PIPE,
391 stderr=subprocess.PIPE,
391 shell=True)
392 shell=True)
392 repo_commit, _ = proc.communicate()
393 repo_commit, _ = proc.communicate()
393 # We write the installation commit even if it's empty
394 # We write the installation commit even if it's empty
394 cfg_parser = ConfigParser()
395 cfg_parser = ConfigParser()
395 cfg_parser.read(pjoin(pkg_dir, '.git_commit_info.ini'))
396 cfg_parser.read(pjoin(pkg_dir, '.git_commit_info.ini'))
396 cfg_parser.set('commit hash', 'install_hash', repo_commit)
397 cfg_parser.set('commit hash', 'install_hash', repo_commit)
397 out_pth = pjoin(self.build_lib, pkg_dir, '.git_commit_info.ini')
398 out_pth = pjoin(self.build_lib, pkg_dir, '.git_commit_info.ini')
398 out_file = open(out_pth, 'wt')
399 out_file = open(out_pth, 'wt')
399 cfg_parser.write(out_file)
400 cfg_parser.write(out_file)
400 out_file.close()
401 out_file.close()
401 return MyBuildPy
402 return MyBuildPy
General Comments 0
You need to be logged in to leave comments. Login now