Closed chadwhitacre closed 8 years ago
~9 minutes on a SSD: https://github.com/CenterForOpenScience/osf.io/issues/4762#issuecomment-165256051.
17m 44s at Travis.
Looking into this ...
I'm starting with getting a profiling framework in place.
[osf.io]$ python -m cProfile -o profile.dat env/bin/nosetests tests/test_app.py
[website.util.paths] WARNING: Skipping load of "webpack-assets.json" in DEBUG_MODE.
[website.project.metadata.authorizers] INFO: No local.json found to populate lists of DraftRegistrationApproval authorizers.
[website.addons.github.settings] WARNING: No local.py settings file found
[website.addons.osfstorage.settings] WARNING: No local.py settings file found
[website.addons.wiki.settings] WARNING: No local.py settings file found
[root] DEBUG: Setting storage backends
[root] INFO: Sentry disabled; Flask's debug mode enabled
[root] INFO: Sentry disabled; Flask's debug mode enabled
.
----------------------------------------------------------------------
Ran 1 test in 0.003s
OK
[osf.io]$
I'd like to look both at startup time for the entire test suite, and then obviously hotspots in tests themselves.
@erinspace reports that ... James? recently profiled and optimized the test suite over on SHARE.
Not seeing a PR for that in https://github.com/CenterForOpenScience/SHARE/pulls?q=is%3Apr+is%3Aclosed.
The following are some interesting calls to experiment with:
p.sort_stats('cumulative').print_stats(10)
This sorts the profile by cumulative time in a function, and then only prints the ten most significant lines. If you want to understand what algorithms are taking time, the above line is what you would use.
If you were looking to see what functions were looping a lot, and taking a lot of time, you would do:
p.sort_stats('time').print_stats(10)
to sort according to time spent within each function, and then print the statistics for the top ten functions.
https://docs.python.org/2/library/profile.html#instant-user-s-manual
It was part of a much larger PR made in a hackathon on scrapi -- the real code behind SHARE: https://github.com/CenterForOpenScience/scrapi/pull/244/files I think he did a big overhaul including using tox for test running (which was possible because scrapi is very separate from the OSF)
Cool, thanks @erinspace. :-)
Invoked as a script, the pstats module is a statistics browser for reading and examining profile dumps. It has a simple line-oriented interface (implemented using cmd) and interactive help.
end of https://docs.python.org/2/library/profile.html#instant-user-s-manual
So ...
Yesssssssssssss ...
[osf.io]$ python -m pstats profile.dat
Welcome to the profile statistics browser.
profile.dat% ?
Documented commands (type help <topic>):
========================================
EOF add callees callers help quit read reverse sort stats strip
profile.dat% sort cumulative
profile.dat% stats 10
Thu Dec 17 13:44:38 2015 profile.dat
3136876 function calls (3093717 primitive calls) in 9.313 seconds
Ordered by: cumulative time
List reduced from 8626 to 10 due to restriction <10>
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.032 0.032 9.319 9.319 env/bin/nosetests:3(<module>)
1 0.000 0.000 8.703 8.703 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/nose/core.py:102(__init__)
1 0.000 0.000 8.702 8.702 /Users/whit537/lib/python2.7/unittest/main.py:72(__init__)
1 0.000 0.000 8.699 8.699 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/nose/core.py:142(parseArgs)
1 0.000 0.000 8.584 8.584 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/nose/core.py:181(createTests)
1 0.000 0.000 8.584 8.584 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/nose/loader.py:469(loadTestsFromNames)
1 0.000 0.000 8.584 8.584 /Users/whit537/lib/python2.7/unittest/loader.py:126(loadTestsFromNames)
1 0.000 0.000 8.584 8.584 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/nose/loader.py:361(loadTestsFromName)
1 0.000 0.000 8.582 8.582 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/nose/importer.py:34(importFromPath)
1 0.000 0.000 8.582 8.582 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/nose/importer.py:49(importFromDir)
profile.dat%
Okay! I've kicked off a full test run, and then we should have some interesting profiling data to explore! :-)
Thu Dec 17 14:02:06 2015 profile.dat
156687425 function calls (152033491 primitive calls) in 682.988 seconds
Ordered by: cumulative time
List reduced from 16670 to 30 due to restriction <30>
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.028 0.028 683.061 683.061 nosetests:3(<module>)
1 0.000 0.000 682.527 682.527 core.py:102(__init__)
1 0.000 0.000 682.526 682.526 main.py:72(__init__)
1 0.000 0.000 672.944 672.944 core.py:195(runTests)
1 0.000 0.000 672.944 672.944 core.py:46(run)
382/1 0.005 0.000 672.943 672.943 suite.py:176(__call__)
382/1 0.024 0.000 672.943 672.943 suite.py:197(run)
1736 0.007 0.000 452.909 0.261 case.py:44(__call__)
1736 0.020 0.000 452.901 0.261 case.py:115(run)
1736 0.019 0.000 452.583 0.261 case.py:142(runTest)
1736 0.007 0.000 452.558 0.261 case.py:392(__call__)
1736 0.077 0.000 452.550 0.261 case.py:295(run)
304728 326.892 0.001 326.892 0.001 {method 'recv' of '_socket.socket' objects}
5069/4929 0.050 0.000 320.983 0.065 base.py:72(__call__)
6121/4929 0.057 0.000 320.935 0.065 base.py:511(create)
6159/5959 0.220 0.000 319.128 0.054 base.py:418(_generate)
853013/163197 2.413 0.000 271.321 0.002 storedobject.py:101(wrapped)
290683/162697 2.957 0.000 269.015 0.002 storedobject.py:76(wrapped)
6159/5975 0.183 0.000 250.883 0.042 base.py:390(_prepare)
147874 1.824 0.000 244.552 0.002 mongo_client.py:949(__receive_message_on_socket)
295748 1.922 0.000 242.287 0.001 mongo_client.py:934(__receive_data_on_socket)
79355/59779 5.301 0.000 224.279 0.004 storedobject.py:762(save)
648 0.008 0.000 219.913 0.339 util.py:441(try_run)
102117 1.804 0.000 195.512 0.002 mongo_client.py:888(_send_message)
36419/28546 0.276 0.000 191.935 0.007 model.py:92(save)
5594 0.103 0.000 181.126 0.032 factories.py:84(_create)
16983/8546 0.696 0.000 169.357 0.020 model.py:1351(save)
382 0.010 0.000 162.673 0.426 suite.py:268(setUp)
310 0.004 0.000 162.650 0.525 suite.py:300(setupContext)
19273/19201 0.726 0.000 161.838 0.008 core.py:1046(save)
The recv
call jumps out at me. I notice below that we've got several calls in mongo_client
. Can we avoid calling to mongo so much?
With paths intact:
Thu Dec 17 14:02:06 2015 profile.dat
156687425 function calls (152033491 primitive calls) in 682.988 seconds
Ordered by: cumulative time
List reduced from 17531 to 30 due to restriction <30>
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.028 0.028 683.061 683.061 env/bin/nosetests:3(<module>)
1 0.000 0.000 682.527 682.527 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/nose/core.py:102(__init__)
1 0.000 0.000 682.526 682.526 /Users/whit537/lib/python2.7/unittest/main.py:72(__init__)
1 0.000 0.000 672.944 672.944 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/nose/core.py:195(runTests)
1 0.000 0.000 672.944 672.944 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/nose/core.py:46(run)
382/1 0.005 0.000 672.943 672.943 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/nose/suite.py:176(__call__)
382/1 0.024 0.000 672.943 672.943 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/nose/suite.py:197(run)
1736 0.007 0.000 452.909 0.261 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/nose/case.py:44(__call__)
1736 0.020 0.000 452.901 0.261 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/nose/case.py:115(run)
1736 0.019 0.000 452.583 0.261 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/nose/case.py:142(runTest)
1736 0.007 0.000 452.558 0.261 /Users/whit537/lib/python2.7/unittest/case.py:392(__call__)
1736 0.077 0.000 452.550 0.261 /Users/whit537/lib/python2.7/unittest/case.py:295(run)
304728 326.892 0.001 326.892 0.001 {method 'recv' of '_socket.socket' objects}
5069/4929 0.050 0.000 320.983 0.065 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/factory/base.py:72(__call__)
6121/4929 0.057 0.000 320.935 0.065 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/factory/base.py:511(create)
6159/5959 0.220 0.000 319.128 0.054 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/factory/base.py:418(_generate)
853013/163197 2.413 0.000 271.321 0.002 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/modularodm/storedobject.py:101(wrapped)
290683/162697 2.957 0.000 269.015 0.002 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/modularodm/storedobject.py:76(wrapped)
6159/5975 0.183 0.000 250.883 0.042 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/factory/base.py:390(_prepare)
147874 1.824 0.000 244.552 0.002 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/pymongo/mongo_client.py:949(__receive_message_on_socket)
295748 1.922 0.000 242.287 0.001 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/pymongo/mongo_client.py:934(__receive_data_on_socket)
79355/59779 5.301 0.000 224.279 0.004 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/modularodm/storedobject.py:762(save)
648 0.008 0.000 219.913 0.339 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/nose/util.py:441(try_run)
102117 1.804 0.000 195.512 0.002 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/pymongo/mongo_client.py:888(_send_message)
36419/28546 0.276 0.000 191.935 0.007 /Users/whit537/workbench/cos/osf.io/framework/guid/model.py:92(save)
5594 0.103 0.000 181.126 0.032 /Users/whit537/workbench/cos/osf.io/tests/factories.py:84(_create)
16983/8546 0.696 0.000 169.357 0.020 /Users/whit537/workbench/cos/osf.io/website/project/model.py:1351(save)
382 0.010 0.000 162.673 0.426 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/nose/suite.py:268(setUp)
310 0.004 0.000 162.650 0.525 /Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/nose/suite.py:300(setupContext)
19273/19201 0.726 0.000 161.838 0.008 /Users/whit537/workbench/cos/osf.io/framework/auth/core.py:1046(save)
I'm poking around in the testing and mongo infrastructure, trying to formulate a question ...
save
is implicated, but what about reads (recv
)?
I want to understand the structure of the test suite and its setup/teardown. I also want to understand what the pathways to mongo are in the test suite and how frequently each is called.
For the latter I'm thinking of adding print statements in storedobject.py
and rerunning the test suite. For the former I need to read code ...
I count 27 calls to StoredObject.save
on test suite initialization, in order to run one test.
Alright, so I'm seeing OsfTestCase
that subclasses DbTestCase
in tests/base.py
. I'm isolating this as a test which exercises DbTestCase
:
[osf.io]$ nosetests tests/test_guids.py:TestGuidStoredObject -s
[website.util.paths] WARNING: Skipping load of "webpack-assets.json" in DEBUG_MODE.
[website.project.metadata.authorizers] INFO: No local.json found to populate lists of DraftRegistrationApproval authorizers.
[website.addons.github.settings] WARNING: No local.py settings file found
[website.addons.osfstorage.settings] WARNING: No local.py settings file found
[website.addons.wiki.settings] WARNING: No local.py settings file found
[root] DEBUG: Setting storage backends
[root] INFO: Sentry disabled; Flask's debug mode enabled
[root] INFO: Sentry disabled; Flask's debug mode enabled
.
----------------------------------------------------------------------
Ran 1 test in 0.657s
OK
[osf.io]$
Let's log some save
calls ...
Okay! Here's a patch for env/lib/python2.7/site-packages/modularodm/storedobject.py
:
--- storedobject.py.bak 2015-12-17 15:20:55.000000000 -0500
+++ storedobject.py 2015-12-17 15:21:02.000000000 -0500
@@ -19,6 +19,11 @@
logger = logging.getLogger(__name__)
+import inspect
+OSF_IO_BASE = '/Users/whit537/workbench/cos/osf.io/'
+save_log = open('save.log', 'w+')
+
+
class ContextLogger(object):
@staticmethod
@@ -768,6 +773,21 @@
back-references
:returns: List of changed fields
"""
+ frames = inspect.stack()
+ entry = exit_ = prev = ''
+ for frame in frames:
+ loc = (frame[1][len(OSF_IO_BASE):], frame[2])
+ if loc[0].startswith('env/lib/python2.7/'):
+ continue
+ elif exit_ and loc[0].startswith('tests/'):
+ entry = prev
+ break
+ elif not exit_:
+ exit_ = loc
+ prev = loc
+ fmt = lambda loc: "{}:{}".format(*loc)
+ print >> save_log, "{:<50}{:<50}".format(fmt(entry), fmt(exit_))
+
if self._detached:
raise exceptions.DatabaseError('Cannot save detached object.')
And here's some sample output:
[osf.io]$ nosetests tests/test_guids.py:TestGuidStoredObject -s
[website.util.paths] WARNING: Skipping load of "webpack-assets.json" in DEBUG_MODE.
[website.project.metadata.authorizers] INFO: No local.json found to populate lists of DraftRegistrationApproval authorizers.
[website.addons.github.settings] WARNING: No local.py settings file found
[website.addons.osfstorage.settings] WARNING: No local.py settings file found
[website.addons.wiki.settings] WARNING: No local.py settings file found
[root] DEBUG: Setting storage backends
[root] INFO: Sentry disabled; Flask's debug mode enabled
[root] INFO: Sentry disabled; Flask's debug mode enabled
.
----------------------------------------------------------------------
Ran 1 test in 0.611s
OK
[osf.io]$ cat save.log
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
framework/guid/model.py:94 framework/guid/model.py:82
framework/guid/model.py:95 framework/guid/model.py:95
[osf.io]$
Seeing a bunch of:
======================================================================
ERROR: test_no_warning_for_read_only_user_with_valid_link (tests.webtest_tests.TestPrivateLinkView)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/whit537/workbench/cos/osf.io/tests/webtest_tests.py", line 363, in setUp
self.link.save()
File "/Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/modularodm/storedobject.py", line 115, in wrapped
return func(*args, **kwargs)
File "/Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/modularodm/storedobject.py", line 87, in wrapped
return func(this, *args, **kwargs)
File "/Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/modularodm/storedobject.py", line 789, in save
print >> save_log, "{:<50}{:<50}".format(fmt(entry), fmt(exit_))
File "/Users/whit537/workbench/cos/osf.io/env/lib/python2.7/site-packages/modularodm/storedobject.py", line 788, in <lambda>
fmt = lambda loc: "{}:{}".format(*loc)
IndexError: tuple index out of range
Fixed. New diff:
--- storedobject.py.bak 2015-12-17 15:20:55.000000000 -0500
+++ env/lib/python2.7/site-packages/modularodm/storedobject.py 2015-12-17 15:59:23.000000000 -0500
@@ -19,6 +19,11 @@
logger = logging.getLogger(__name__)
+import inspect
+OSF_IO_BASE = '/Users/whit537/workbench/cos/osf.io/'
+save_log = open('save.log', 'w+')
+
+
class ContextLogger(object):
@staticmethod
@@ -768,6 +773,21 @@
back-references
:returns: List of changed fields
"""
+ frames = inspect.stack()
+ entry = exit_ = prev = None
+ for frame in frames:
+ loc = (frame[1][len(OSF_IO_BASE):], frame[2])
+ if loc[0].startswith('env/lib/python2.7/'):
+ continue
+ elif exit_ and loc[0].startswith('tests/'):
+ entry = prev
+ break
+ elif not exit_:
+ exit_ = loc
+ prev = loc
+ fmt = lambda loc: "{}:{}".format(*loc) if loc else '-'
+ print >> save_log, "{:<50}{:<50}".format(fmt(entry), fmt(exit_))
+
if self._detached:
raise exceptions.DatabaseError('Cannot save detached object.')
Rerunning ...
------ Entries (N=271) -----------------
17,827 framework/auth/core.py:1051
14,672 website/project/model.py:1377
12,992 website/project/model.py:1371
6,522 website/project/model.py:1397
3,132 website/project/model.py:2107
2,311 framework/routing/__init__.py:111
2,142 website/project/model.py:2118
1,613 website/project/model.py:2128
1,474 website/project/model.py:144
1,447 website/project/model.py:3269
1,422 website/project/model.py:2136
1,242 website/project/model.py:1388
1,202 scripts/parse_citation_styles.py:49
867 website/project/model.py:3278
684 -
547 tests/factories.py:88
447 framework/sessions/__init__.py:185
445 framework/sessions/__init__.py:166
373 website/project/model.py:2803
346 website/project/model.py:2133
336 website/project/model.py:3664
324 tests/factories.py:251
322 tests/factories.py:264
322 tests/factories.py:262
273 website/project/licenses/__init__.py:112
217 website/project/model.py:3233
214 website/project/model.py:2047
200 framework/guid/model.py:95
195 website/project/model.py:2806
182 website/project/model.py:3176
171 website/project/model.py:3010
148 website/project/model.py:1583
144 website/archiver/utils.py:141
140 website/project/model.py:2050
132 website/project/model.py:1518
130 framework/addons/__init__.py:111
126 website/project/model.py:2888
123 website/project/model.py:3242
123 website/project/model.py:2873
118 framework/addons/__init__.py:98
118 website/project/model.py:3031
105 website/project/model.py:3183
104 website/project/model.py:2437
104 website/project/model.py:2251
103 website/project/model.py:3953
99 framework/guid/model.py:94
98 website/project/model.py:1561
96 website/project/model.py:4297
94 website/project/model.py:2936
91 website/project/model.py:1998
88 website/project/model.py:2450
81 website/addons/github/model.py:98
81 website/project/model.py:2175
77 website/project/model.py:4246
72 website/project/model.py:1512
62 tests/factories.py:325
60 website/project/views/node.py:703
59 website/archiver/model.py:185
56 website/project/model.py:3029
54 website/project/model.py:1490
54 website/addons/github/model.py:97
52 website/project/model.py:2248
50 website/project/model.py:4140
49 website/project/model.py:1578
48 framework/auth/core.py:1317
46 website/project/model.py:2933
46 website/project/model.py:3814
44 website/project/model.py:2448
40 website/project/model.py:2886
39 website/project/model.py:4060
38 framework/tasks/handlers.py:29
36 website/archiver/utils.py:135
35 website/project/model.py:3187
30 website/project/model.py:1753
30 website/project/model.py:3565
30 website/conferences/utils.py:63
28 website/project/model.py:3831
28 website/project/model.py:3507
28 website/project/model.py:2161
27 scripts/parse_citation_styles.py:14
27 api/base/wsgi.py:16
27 website/project/model.py:2172
27 website/project/model.py:2572
27 website/project/model.py:4057
24 website/project/model.py:2044
24 website/project/model.py:3245
23 website/tokens/__init__.py:51
22 website/files/models/base.py:168
22 website/project/model.py:3555
21 framework/auth/core.py:1286
21 website/addons/wiki/model.py:219
20 website/archiver/listeners.py:47
20 website/project/model.py:1048
20 website/addons/base/__init__.py:869
20 website/project/model.py:1330
20 website/archiver/listeners.py:24
19 website/archiver/model.py:156
19 website/files/models/base.py:356
19 website/project/model.py:1521
18 website/project/model.py:1952
18 website/conferences/utils.py:76
18 website/project/model.py:4046
17 website/conferences/utils.py:82
17 website/files/models/base.py:629
16 website/project/model.py:4154
16 website/project/model.py:1229
15 website/archiver/model.py:186
15 website/project/model.py:2938
15 website/conferences/utils.py:83
14 lib.py:17
13 website/mails/queued_mails.py:90
12 website/project/model.py:3114
12 website/project/model.py:1942
12 website/project/model.py:1896
12 website/oauth/models/__init__.py:275
12 framework/auth/core.py:1099
12 website/files/models/osfstorage.py:153
12 website/project/model.py:2154
11 website/project/model.py:787
10 website/project/model.py:3068
10 website/project/model.py:1957
10 website/project/model.py:3094
10 website/addons/base/__init__.py:870
9 website/project/model.py:2752
9 framework/auth/core.py:1297
9 website/project/model.py:2569
9 website/project/model.py:4063
9 website/project/model.py:1505
9 website/project/model.py:3956
8 website/identifiers/model.py:44
8 website/project/model.py:3000
8 framework/auth/__init__.py:47
8 framework/auth/__init__.py:46
8 website/project/model.py:1226
8 website/project/model.py:4048
8 website/project/model.py:1778
8 tests/test_addons.py:532
7 website/project/model.py:2882
7 website/project/model.py:2852
7 framework/auth/core.py:1292
6 website/project/model.py:232
6 website/project/model.py:1348
6 website/project/model.py:3112
6 website/project/model.py:3207
6 tests/test_addons.py:188
6 website/addons/base/__init__.py:377
6 tests/test_addons.py:103
6 website/project/model.py:2575
6 website/archiver/utils.py:77
6 tests/test_addons.py:179
6 website/mails/queued_mails.py:47
6 website/project/model.py:2808
6 tests/test_addons.py:94
5 website/addons/base/__init__.py:859
5 framework/auth/core.py:858
5 framework/auth/core.py:515
5 website/project/model.py:1288
5 website/addons/base/__init__.py:862
5 website/project/model.py:3092
5 website/project/model.py:1829
4 website/project/model.py:1893
4 website/archiver/model.py:175
4 framework/auth/cas.py:227
4 website/project/model.py:542
4 website/archiver/tasks.py:342
4 website/project/model.py:1920
4 website/project/model.py:3254
4 website/files/models/base.py:379
4 framework/addons/__init__.py:128
4 website/project/model.py:2476
4 website/project/model.py:3156
4 website/archiver/model.py:174
4 website/conferences/utils.py:80
4 website/project/model.py:2151
4 website/project/model.py:2463
4 website/project/model.py:4301
3 website/project/model.py:3932
3 website/notifications/utils.py:150
3 website/project/views/contributor.py:270
3 website/project/model.py:3213
3 website/project/__init__.py:140
3 tests/factories.py:541
3 website/project/model.py:3212
3 website/project/model.py:3209
3 website/project/model.py:312
3 website/project/views/contributor.py:257
3 website/project/model.py:1296
3 framework/auth/core.py:1113
3 website/project/model.py:3825
3 website/archiver/listeners.py:58
3 website/project/model.py:1598
3 website/conferences/utils.py:88
3 website/files/models/base.py:529
3 website/project/views/contributor.py:519
3 website/archiver/utils.py:76
3 framework/auth/core.py:1246
3 website/project/views/contributor.py:268
2 website/project/model.py:1773
2 website/project/views/contributor.py:399
2 website/project/model.py:538
2 website/notifications/events/files.py:254
2 website/project/model.py:1269
2 website/project/model.py:3259
2 website/project/model.py:3256
2 website/project/model.py:2699
2 website/profile/views.py:567
2 framework/tasks/utils.py:41
2 website/project/model.py:246
2 website/project/model.py:3158
2 website/project/model.py:1856
2 website/oauth/models/__init__.py:431
2 website/project/model.py:278
2 website/project/model.py:3162
2 website/project/views/contributor.py:419
2 website/project/model.py:3161
2 website/project/model.py:2474
2 website/project/model.py:1782
2 website/project/model.py:1832
2 framework/auth/__init__.py:78
2 website/project/model.py:1006
2 website/project/views/contributor.py:464
2 website/files/models/base.py:623
2 website/project/model.py:3260
1 website/project/model.py:1010
1 website/project/model.py:2816
1 website/project/model.py:262
1 website/project/model.py:1959
1 website/project/model.py:2011
1 framework/auth/cas.py:226
1 website/project/model.py:2838
1 website/project/model.py:2690
1 website/archiver/utils.py:108
1 website/identifiers/model.py:49
1 framework/auth/core.py:771
1 website/project/model.py:1256
1 website/project/model.py:1266
1 website/project/model.py:243
1 website/project/model.py:3827
1 website/addons/base/__init__.py:888
1 framework/auth/decorators.py:37
1 website/util/__init__.py:162
1 website/project/model.py:4165
1 website/project/model.py:2730
1 framework/auth/core.py:783
1 website/project/model.py:2908
1 website/project/model.py:2105
1 website/project/model.py:2513
1 website/oauth/models/__init__.py:506
1 website/files/models/base.py:620
1 website/mails/queued_mails.py:38
1 website/project/model.py:1290
1 website/archiver/decorators.py:20
1 website/project/model.py:1614
1 website/project/model.py:4159
1 website/project/views/contributor.py:453
1 website/project/model.py:1475
1 website/files/models/osfstorage.py:149
1 framework/auth/core.py:1272
1 framework/auth/core.py:989
1 website/files/models/base.py:624
1 website/files/models/base.py:494
1 website/project/model.py:294
1 framework/auth/core.py:1247
1 website/project/model.py:2653
1 website/project/licenses/__init__.py:73
1 website/project/model.py:327
1 website/files/models/base.py:380
1 website/files/models/base.py:78
1 website/project/model.py:4288
1 website/project/model.py:4285
1 website/project/model.py:2647
79,355 Σ
------ Exits (N=293) -------------------
41,220 framework/guid/model.py:95
6,059 framework/guid/model.py:40
6,047 framework/guid/model.py:88
4,472 framework/addons/__init__.py:98
3,647 website/project/model.py:2248
2,674 website/files/models/base.py:356
2,590 website/addons/osfstorage/model.py:36
2,590 website/addons/osfstorage/model.py:41
1,496 website/project/model.py:136
1,202 scripts/parse_citation_styles.py:49
736 website/project/model.py:3664
580 website/archiver/model.py:156
547 tests/factories.py:88
485 website/archiver/model.py:175
447 framework/sessions/__init__.py:185
406 website/addons/osfstorage/model.py:59
406 website/addons/wiki/model.py:84
324 tests/factories.py:251
322 tests/factories.py:264
322 tests/factories.py:262
305 website/project/licenses/__init__.py:112
297 website/project/model.py:3260
297 website/project/model.py:3254
116 website/addons/github/model.py:98
78 website/project/model.py:4246
68 website/mails/queued_mails.py:90
64 website/project/model.py:2161
63 website/project/model.py:1561
62 tests/factories.py:325
60 website/archiver/model.py:185
55 website/addons/github/model.py:97
54 tests/test_conferences.py:423
51 website/project/model.py:3207
51 website/project/model.py:3213
45 website/project/model.py:3156
44 website/project/model.py:3162
33 website/addons/base/__init__.py:661
33 website/addons/osfstorage/model.py:51
33 website/addons/osfstorage/model.py:46
24 tests/test_notifications.py:367
24 tests/test_notifications.py:386
24 tests/test_notifications.py:384
24 tests/test_notifications.py:365
24 tests/test_notifications.py:377
24 tests/test_notifications.py:375
20 tests/test_models.py:3497
20 website/archiver/listeners.py:47
18 tests/test_notifications.py:983
18 tests/test_notifications.py:981
18 tests/test_notifications.py:991
17 framework/auth/core.py:1099
16 tests/test_rubeus.py:46
15 website/archiver/model.py:144
15 tests/test_citations.py:35
14 website/project/model.py:3507
14 website/project/views/drafts.py:169
14 tests/utils.py:116
13 tests/test_addons.py:522
13 tests/test_addons.py:514
12 tests/test_views.py:94
12 website/identifiers/model.py:44
12 framework/guid/model.py:46
12 website/project/model.py:4063
12 tests/models/test_addon_settings.py:40
11 website/addons/base/__init__.py:377
11 website/project/model.py:3827
10 website/addons/base/__init__.py:870
10 framework/sessions/__init__.py:101
9 tests/test_views.py:2524
9 website/project/model.py:538
9 framework/auth/core.py:515
9 tests/test_notifications.py:767
9 tests/test_notifications.py:761
9 tests/test_addons.py:752
9 tests/test_notifications.py:755
9 tests/test_notifications.py:870
9 tests/test_notifications.py:876
9 tests/test_addons.py:532
8 website/tokens/handlers.py:89
7 website/files/models/base.py:424
7 website/conferences/views.py:249
7 tests/models/test_addon_settings.py:41
7 website/notifications/model.py:59
7 website/project/model.py:4215
6 tests/test_alternative_citations.py:19
6 tests/test_addons.py:188
6 tests/test_addons.py:103
6 tests/test_addons.py:171
6 tests/test_addons.py:179
6 website/mails/queued_mails.py:47
6 tests/test_addons.py:85
6 website/project/model.py:4301
6 tests/test_websitefiles.py:63
6 tests/test_addons.py:94
5 website/project/model.py:3945
5 tests/test_registrations/test_archiver.py:1048
5 tests/test_events.py:359
5 website/addons/base/__init__.py:862
5 tests/test_events.py:349
5 tests/test_events.py:342
5 tests/test_registrations/test_archiver.py:1056
4 tests/test_notifications.py:221
4 tests/test_notifications.py:224
4 tests/test_models.py:517
4 tests/test_events.py:288
4 tests/test_notifications.py:212
4 tests/test_registrations/test_models.py:137
4 tests/test_events.py:297
4 tests/test_events.py:519
4 tests/webtest_tests.py:363
4 tests/test_events.py:536
4 tests/test_notifications.py:209
4 website/project/licenses/__init__.py:73
4 tests/test_registrations/test_models.py:143
4 website/archiver/utils.py:76
4 tests/test_events.py:526
3 tests/test_tokens.py:119
3 tests/test_events.py:223
3 tests/factories.py:541
3 website/addons/base/__init__.py:888
3 website/files/models/base.py:430
3 website/addons/github/model.py:491
3 tests/test_events.py:444
3 tests/test_tokens.py:133
3 website/notifications/views.py:106
3 website/addons/base/__init__.py:213
3 website/notifications/model.py:42
3 tests/test_events.py:454
3 website/oauth/models/__init__.py:310
3 tests/test_events.py:437
3 website/oauth/models/__init__.py:285
2 website/archiver/listeners.py:69
2 tests/test_events.py:196
2 tests/test_events.py:170
2 website/oauth/models/__init__.py:431
2 tests/test_events.py:258
2 tests/test_auth.py:155
2 website/project/model.py:4288
2 website/project/model.py:4285
1 tests/test_notifications.py:1189
1 tests/test_registrations/test_retractions.py:579
1 tests/test_notifications.py:836
1 tests/test_websitefiles.py:198
1 tests/test_views.py:862
1 tests/test_events.py:547
1 tests/test_views.py:1251
1 tests/test_oauth.py:268
1 tests/test_events.py:549
1 tests/test_elastic.py:285
1 tests/test_websitefiles.py:518
1 tests/test_notifications.py:788
1 website/addons/base/__init__.py:399
1 tests/test_events.py:380
1 tests/test_notifications.py:1248
1 tests/test_notifications.py:1030
1 tests/test_notifications.py:1240
1 tests/test_elastic.py:850
1 tests/test_oauth.py:475
1 tests/test_notifications.py:802
1 website/project/views/node.py:1135
1 website/project/__init__.py:140
1 tests/test_views.py:166
1 tests/test_elastic.py:859
1 website/project/views/node.py:662
1 tests/test_models.py:2524
1 tests/test_websitefiles.py:163
1 tests/test_events.py:372
1 tests/test_models.py:1441
1 tests/test_notifications.py:1256
1 tests/test_websitefiles.py:366
1 tests/test_notifications.py:795
1 tests/test_models.py:2529
1 tests/test_websitefiles.py:365
1 tests/test_registrations/test_sanctions.py:246
1 website/project/model.py:1256
1 website/conferences/utils.py:24
1 website/mails/presends.py:56
1 tests/test_conferences.py:375
1 website/addons/github/model.py:199
1 website/identifiers/model.py:49
1 website/project/model.py:4159
1 tests/test_oauth.py:443
1 tests/test_notifications.py:845
1 tests/test_notifications.py:847
1 tests/test_registrations/test_models.py:85
1 tests/webtest_tests.py:378
1 tests/test_registrations/test_archiver.py:1155
1 tests/test_events.py:579
1 tests/test_notifications.py:1205
1 tests/test_events.py:476
1 tests/test_conferences.py:515
1 tests/test_notifications.py:1007
1 tests/test_notifications.py:918
1 tests/test_notifications.py:925
1 tests/test_notifications.py:681
1 tests/test_registrations/test_sanctions.py:93
1 tests/test_addons.py:727
1 tests/test_notifications.py:588
1 tests/test_models.py:4242
1 tests/test_registrations/test_retractions.py:529
1 tests/test_models.py:4252
1 tests/test_notifications.py:260
1 tests/test_registrations/test_models.py:106
1 tests/test_notifications.py:559
1 website/project/model.py:4165
1 tests/test_websitefiles.py:389
1 tests/test_views.py:183
1 website/project/model.py:2908
1 tests/test_notifications.py:460
1 tests/test_events.py:567
1 tests/test_models.py:3437
1 tests/test_notifications.py:1197
1 tests/test_prereg.py:74
1 tests/test_elastic.py:343
1 framework/guid/model.py:82
1 tests/test_notifications.py:939
1 tests/test_notifications.py:262
1 tests/test_notifications.py:781
1 tests/test_registrations/test_models.py:118
1 tests/test_registrations/test_views.py:490
1 tests/test_websitefiles.py:74
1 tests/test_auth_basic_auth.py:56
1 website/oauth/models/__init__.py:506
1 tests/test_models.py:848
1 tests/test_conferences.py:187
1 tests/test_views.py:1640
1 tests/test_models.py:2518
1 tests/test_alternative_citations.py:31
1 tests/test_notifications.py:820
1 tests/test_notifications.py:826
1 tests/test_events.py:408
1 tests/test_views.py:1255
1 tests/test_notifications.py:953
1 tests/test_addons.py:660
1 tests/test_registrations/test_models.py:166
1 tests/test_rubeus.py:250
1 tests/test_notifications.py:899
1 website/project/views/drafts.py:300
1 tests/test_oauth.py:520
1 tests/test_registrations/test_models.py:168
1 tests/test_notifications.py:890
1 tests/test_notifications.py:446
1 tests/test_websitefiles.py:154
1 tests/test_registrations/test_archiver.py:908
1 tests/test_registrations/test_sanctions.py:86
1 tests/test_project_decorators.py:70
1 tests/test_notifications.py:448
1 tests/test_notifications.py:1292
1 tests/test_alternative_citations.py:24
1 tests/test_notifications.py:813
1 tests/test_views.py:111
1 tests/test_notifications.py:811
1 tests/test_alternative_citations.py:38
1 tests/test_events.py:480
1 tests/test_models.py:4263
1 tests/test_views.py:120
1 tests/test_events.py:489
1 tests/test_notifications.py:966
1 tests/test_views.py:1276
1 tests/test_guids.py:50
1 tests/test_views.py:1272
1 tests/test_registrations/test_archiver.py:896
1 tests/test_events.py:565
1 tests/test_notifications.py:432
1 tests/test_notifications.py:434
1 website/addons/base/__init__.py:467
1 website/files/models/base.py:717
1 tests/test_registrations/test_archiver.py:932
1 tests/test_websitefiles.py:530
1 tests/test_notifications.py:804
1 tests/test_alternative_citations.py:52
1 tests/test_views.py:101
1 framework/auth/core.py:1247
1 tests/test_alternative_citations.py:59
1 tests/test_registrations/test_sanctions.py:234
1 tests/test_auth_basic_auth.py:78
1 tests/test_websitefiles.py:183
1 tests/test_views.py:877
1 website/files/models/base.py:555
1 tests/test_oauth.py:310
1 tests/test_oauth.py:413
1 tests/test_notifications.py:1087
1 tests/test_events.py:398
1 tests/test_events.py:394
1 tests/test_models.py:1076
1 tests/test_auth_basic_auth.py:67
1 website/addons/base/__init__.py:482
1 website/addons/base/__init__.py:480
1 tests/test_models.py:2665
1 tests/test_notifications.py:1028
1 tests/test_alternative_citations.py:45
1 tests/test_models.py:810
1 tests/test_views.py:3853
79,355 Σ
#!/usr/bin/env python
# encoding: utf-8
from __future__ import absolute_import, division, print_function, unicode_literals
from collections import defaultdict
entries = defaultdict(int)
exits = defaultdict(int)
for line in open('save.log'):
if not line:
continue
entry, exit_ = line.split()
entries[entry] += 1
exits[exit_] += 1
for name, d in (("Entries", entries), ("Exits", exits)):
title = "------ {} (N={:,}) ".format(name, len(d))
total = 0
print(title.ljust(40, '-'))
print()
for k,v in reversed(sorted(d.items(), key=lambda o: o[1])):
print("{:>6,} {}".format(v, k))
total += v
print('{:>6,} {}'.format(total, 'Σ').encode('utf8'))
print()
print()
"Entries" means the first frame outside of the test suite, and "Exits" are the last frame before descending into storedobject.py
.
With the following patch, the test suite runs in ~37% of the time it runs without it. However, errors+failures are up to 82%. ;-)
diff --git a/framework/auth/core.py b/framework/auth/core.py
index a738882..2518e78 100644
--- a/framework/auth/core.py
+++ b/framework/auth/core.py
@@ -1046,6 +1046,7 @@ class User(GuidStoredObject, AddonModelMixin):
def save(self, *args, **kwargs):
# TODO: Update mailchimp subscription on username change
# Avoid circular import
+ return
from framework.analytics import tasks as piwik_tasks
self.username = self.username.lower().strip() if self.username else None
ret = super(User, self).save(*args, **kwargs)
------ Entries (N=14) ------------------
1,202 scripts/parse_citation_styles.py:49
418 website/project/model.py:144
27 scripts/parse_citation_styles.py:14
27 api/base/wsgi.py:16
24 tests/factories.py:88
11 -
10 website/project/model.py:3664
8 framework/auth/__init__.py:47
6 website/oauth/models/__init__.py:275
3 tests/factories.py:541
2 framework/auth/core.py:515
2 website/oauth/models/__init__.py:431
1 framework/guid/model.py:95
1 framework/guid/model.py:94
1,742 Σ
------ Exits (N=24) --------------------
1,202 scripts/parse_citation_styles.py:49
440 website/project/model.py:136
32 website/project/licenses/__init__.py:112
24 tests/factories.py:88
10 website/project/model.py:3664
8 framework/sessions/__init__.py:101
3 website/oauth/models/__init__.py:310
3 website/oauth/models/__init__.py:285
3 tests/factories.py:541
2 website/oauth/models/__init__.py:431
2 framework/auth/core.py:515
1 tests/test_oauth.py:443
1 framework/guid/model.py:95
1 tests/test_registrations/test_archiver.py:1155
1 tests/test_oauth.py:268
1 tests/test_conferences.py:515
1 tests/test_models.py:848
1 tests/test_oauth.py:475
1 tests/test_oauth.py:310
1 tests/test_oauth.py:413
1 tests/test_oauth.py:520
1 framework/guid/model.py:82
1 tests/test_models.py:810
1 tests/test_conferences.py:375
1,742 Σ
How about startup time? What does it cost to test the null hypothesis?
[osf.io]$ time nosetests tests/test_null.py
[website.util.paths] WARNING: Skipping load of "webpack-assets.json" in DEBUG_MODE.
[website.project.metadata.authorizers] INFO: No local.json found to populate lists of DraftRegistrationApproval authorizers.
[website.addons.github.settings] WARNING: No local.py settings file found
[website.addons.osfstorage.settings] WARNING: No local.py settings file found
[website.addons.wiki.settings] WARNING: No local.py settings file found
[root] DEBUG: Setting storage backends
[root] INFO: Sentry disabled; Flask's debug mode enabled
[root] INFO: Sentry disabled; Flask's debug mode enabled
E
======================================================================
ERROR: test (tests.test_null.TestNull)
----------------------------------------------------------------------
TypeError: test() takes no arguments (1 given)
-------------------- >> begin captured logging << --------------------
website.util.paths: WARNING: Skipping load of "webpack-assets.json" in DEBUG_MODE.
website.project.metadata.authorizers: INFO: No local.json found to populate lists of DraftRegistrationApproval authorizers.
website.addons.github.settings: WARNING: No local.py settings file found
website.addons.osfstorage.settings: WARNING: No local.py settings file found
website.addons.wiki.settings: WARNING: No local.py settings file found
root: DEBUG: Setting storage backends
root: INFO: Sentry disabled; Flask's debug mode enabled
root: INFO: Sentry disabled; Flask's debug mode enabled
--------------------- >> end captured logging << ---------------------
----------------------------------------------------------------------
Ran 1 test in 0.617s
FAILED (errors=1)
real 0m5.957s
user 0m4.685s
sys 0m0.547s
[osf.io]$
from tests.base import OsfTestCase
class TestNull(OsfTestCase):
def test():
pass
I just double-checked the Gratipay test suite as a benchmark. Over there, I'm seeing ~70 seconds for ~500 tests. Here I'm seeing ~650 seconds for ~1,750 tests. If our test suite ran as fast as Gratipay's, we would expect to see ~250 seconds, roughly four minutes—a 2x to 3x improvement.
It's a normal workflow is to only run one or a few tests during active local development, running the full test suite only before pushing up changes. I'm seeing a similar profile for that case: 2.3 seconds on Gratipay to test the null hypothesis (def test(): pass
) vs. 6.2 seconds here.
I'm going to focus on this latter case first, because I think it will have the greatest impact on developer productivity. Perhaps that'll have a follow-on effect for per-test setup and therefore on overall test suite time, but if not, I propose that our effort is better spent elsewhere than seeking a mere 2-3x improvement in overall test suite time. @JeffSpies mentioned that server restart time is also an issue during development, so I may look at that as well.
We may find that there are optimizations in common between restart time and test suite start time.
On Dec 18, 2015, at 9:29 AM, Chad Whitacre notifications@github.com wrote:
I just double-checked the Gratipay test suite as a benchmark. Over there, I'm seeing ~70 seconds for ~500 tests. Here I'm seeing ~650 seconds for ~1,750 tests. If our test suite ran as fast as Gratipay's, we would expect to see ~250 seconds, roughly four minutes—a 2x to 3x improvement.
It's a normal workflow is to only run one or a few tests during active local development, running the full test suite only before pushing up changes. I'm seeing a similar profile for that case: 2.3 seconds on Gratipay to test the null hypothesis (def test(): pass) vs. 6.2 seconds here.
I'm going to focus on this latter case first, because I think it will have the greatest impact on developer productivity. Perhaps that'll have a follow-on effect for per-test setup and therefore on overall test suite time, but if not, I propose that our effort is better spent elsewhere than seeking a mere 2-3x improvement in overall test suite time. @JeffSpies https://github.com/JeffSpies mentioned that server restart time is also an issue during development, so I may look at that as well.
— Reply to this email directly or view it on GitHub https://github.com/CenterForOpenScience/osf.io/issues/4768#issuecomment-165790496.
If we're lucky. :)
Alright! Let's do this. :-)
tests/test_null.py .
========================================== 1 passed in 7.71 seconds ==========================================
[osf.io]$ cat save.log
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/model.py:136
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
api/base/wsgi.py:16 website/project/licenses/__init__.py:112
I added a blinker signal to GuidStoredObject.save() in a PR that's not been merged yet, for cache invalidation. While I was debugging, it had a log statement in it. For every call to save we were actually saving at least twice, sometimes more than four times. That could explain part of the mongo chatter.
Thanks @cwisecarver. That could certainly help the overall test suite time.
In the cProfile output for the null test, I'm seeing two calls to init_app
, each taking 1.3 seconds:
ncalls tottime percall cumtime percall filename:lineno(function)
2 0.000 0.000 2.543 1.271 /Users/whit537/workbench/cos/osf.io/website/app.py:104(init_app)
Surely we can remove one of those ...
website.project.licenses is looping through a json file and doing individual queries by id instead of doing a 'in' in mongo. Depending on the length of that file I could see 1.3 seconds being saved there.
@cwisecarver Yeah, there's another loop in ensure_schemas
for which we may be able to consolidate the db calls as well.
Importing api.base.wsgi
triggers a call to init_app
, and then importing from tests.base
makes a second call. Can we avoid the first during testing?
Ftr it sounds like there is some template logfile writing(?) that doesn't need to happen as well.
Yeah, init_app
does a lot. When we call it in tests or scripts, we usually only need the storage backends to be set on the models.
What happens if we use the test_app
in the ApiAppTestCase
?
Ooooh!
/me discovers api_tests
First run ...
/me learns about test_travis_$TEST_BUILD
Maybe we can at least lazily init_app
the API app in setUpClass
?
1291 passed, 1 skipped in 353.19 seconds
The test suite takes a loooooooonnnnnggggg time to run. I strongly suspect that we could cut it by an order of magnitude without too much effort. Here's some argument from authority about why fast tests are good:
https://signalvnoise.com/posts/2742-the-road-to-faster-tests