Merge lp:~jml/libdep-service/test-double into lp:libdep-service
- test-double
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~jml/libdep-service/test-double |
Merge into: | lp:libdep-service |
Diff against target: |
540 lines (+449/-2) 9 files modified
buildout.cfg (+3/-0) djlibdep/test_double.py (+99/-0) djlibdep/tests/__init__.py (+3/-1) djlibdep/tests/_djangofixture.py (+86/-0) djlibdep/tests/test_interface.py (+77/-0) djlibdep/tests/test_test_double.py (+132/-0) setup.py (+6/-0) test-double/libdep_double.go (+40/-0) versions.cfg (+3/-1) |
To merge this branch: | bzr merge lp:~jml/libdep-service/test-double |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Canonical Consumer Applications Hackers | Pending | ||
Review via email: mp+126660@code.launchpad.net |
Commit message
Description of the change
WIP
- 61. By Jonathan Lange
-
REFACTOR: Move out root resource creation.
- 62. By Jonathan Lange
-
RED: We can give it a data file, and it serves JSON accordingly
- 63. By Jonathan Lange
-
Comment about where to go once this test passes
- 64. By Jonathan Lange
-
Handle unexpected errors a little better.
- 65. By Jonathan Lange
-
Remind self that it is a bad idea
- 66. By Jonathan Lange
-
Add the argument, pass it through.
- 67. By Jonathan Lange
-
Failing test is now valid.
- 68. By Jonathan Lange
-
Start a resource that can generate the results we want.
- 69. By Jonathan Lange
-
Initial correct implementation and correct test
- 70. By Jonathan Lange
-
Different implementation.
- 71. By Jonathan Lange
-
GREEN: Handle recursive response.
- 72. By Jonathan Lange
-
Really crumby query param support.
- 73. By Jonathan Lange
-
REFACTOR: get path better for tests.
- 74. By Jonathan Lange
-
GREEN: Restore the disabled test. It now works.
- 75. By Jonathan Lange
-
RED: Basic test for API. Fails because we don't have testscenarios set up.
- 76. By Jonathan Lange
-
GREEN: Make the test run against the test double.
- 77. By Jonathan Lange
-
Don't leak processes.
- 78. By Jonathan Lange
-
Add a fixture. Use that.
- 79. By Jonathan Lange
-
Do the mixin thing until we also do testscenarios.
- 80. By Jonathan Lange
-
RED: Failing tests against the real thing.
- 81. By Jonathan Lange
-
Fix process leak.
- 82. By Jonathan Lange
-
Make the test pass:
- set the base_url
- poll a given path, rather than a fixed pkgme-service path
- just check for 200, not for the content - 83. By Jonathan Lange
-
Move the port to the constructor.
- 84. By Jonathan Lange
-
Split poll_until_running into a separate function.
- 85. By Jonathan Lange
-
Parametrize the hard-coded numbers.
- 86. By Jonathan Lange
-
Split the logic of polling out from the thing we're polling for.
- 87. By Jonathan Lange
-
process can be local.
- 88. By Jonathan Lange
-
Use testresources and testscenarios.
- 89. By Jonathan Lange
-
RED: Failing test from James
- 90. By Jonathan Lange
-
Use local branch of pkgme-devportal for now.
- 91. By Jonathan Lange
-
404 stock response if not found.
- 92. By Jonathan Lange
-
Make the test double more informative on 404.
- 93. By Jonathan Lange
-
Annotate problems
- 94. By Jonathan Lange
-
Make the test server a little more self-documenting.
- 95. By Jonathan Lange
-
Simple cleanup
- 96. By Jonathan Lange
-
Simplify by figuring out the data in main, rather than way down the stack.
- 97. By Jonathan Lange
-
make_root_resource is less meaningful now.
- 98. By Jonathan Lange
-
Remove more unnecessary junk
- 99. By Jonathan Lange
-
Print the base URL when starting the server. Makes manual testing easier.
- 100. By Jonathan Lange
-
This ought to work, but doesn't. Need to change data spec format.
- 101. By Jonathan Lange
-
Change the data format to be more verbose, but perhaps easier to specify
for query args. - 102. By Jonathan Lange
-
Clean up XXXs a bit.
- 103. By Jonathan Lange
-
Go from 2.5s to 1s by using testresources properly.
- 104. By Jonathan Lange
-
Done
- 105. By Jonathan Lange
-
RED: Failing test for found data.
- 106. By Jonathan Lange
-
Wrap django fixture in another fixture.
- 107. By Jonathan Lange
-
Tests that return actual data.
- 108. By Jonathan Lange
-
Re-enable tests.
- 109. By Jonathan Lange
-
Bye-bye go
- 110. By Jonathan Lange
-
Used release pkgme-devportal.
Unmerged revisions
Preview Diff
1 | === modified file 'buildout.cfg' |
2 | --- buildout.cfg 2012-09-07 11:54:59 +0000 |
3 | +++ buildout.cfg 2012-09-28 14:14:21 +0000 |
4 | @@ -25,6 +25,7 @@ |
5 | prefer-final = true |
6 | |
7 | develop = . |
8 | + /home/jml/src/pkgme-devportal |
9 | |
10 | [scripts] |
11 | recipe = z3c.recipe.scripts |
12 | @@ -33,6 +34,8 @@ |
13 | pep8 |
14 | pkgme-devportal[testing] |
15 | pyflakes |
16 | + testresources |
17 | + testscenarios |
18 | testtools |
19 | include-site-packages = false |
20 | interpreter = py |
21 | |
22 | === added file 'djlibdep/test_double.py' |
23 | --- djlibdep/test_double.py 1970-01-01 00:00:00 +0000 |
24 | +++ djlibdep/test_double.py 2012-09-28 14:14:21 +0000 |
25 | @@ -0,0 +1,99 @@ |
26 | +import argparse |
27 | +import json |
28 | +from pprint import pformat |
29 | +import sys |
30 | + |
31 | +from twisted.internet import reactor as mod_reactor |
32 | +from twisted.internet.defer import maybeDeferred |
33 | +from twisted.internet.endpoints import TCP4ServerEndpoint |
34 | +from twisted.web.resource import ( |
35 | + NoResource, |
36 | + Resource, |
37 | + ) |
38 | +from twisted.web.server import Site |
39 | +from twisted.web.static import Data |
40 | + |
41 | + |
42 | +# XXX: Global mutable state :( |
43 | +_EXIT_CODE = 0 |
44 | + |
45 | + |
46 | +class StockResponse(Resource): |
47 | + |
48 | + def __init__(self, stock_data): |
49 | + Resource.__init__(self) |
50 | + self._stock_data = stock_data |
51 | + |
52 | + def getChild(self, name, request): |
53 | + if not name: |
54 | + return self |
55 | + full_name = '/'.join([name] + request.postpath) |
56 | + request.postpath = [] |
57 | + value = self._stock_data.get(full_name, []) |
58 | + for args, data in value: |
59 | + if args == request.args: |
60 | + return Data(data.encode('utf8'), 'text/html') |
61 | + return NoResource( |
62 | + '%r (%r) not in %r' % (full_name, request.args, self._stock_data)) |
63 | + |
64 | + def render_GET(self, request): |
65 | + if request.method == 'HEAD': |
66 | + return '' |
67 | + return ( |
68 | + '<html><head><title>Stock responses</title></head>' |
69 | + '<body><h1>Stock responses</h1><pre>%s</pre></body></html>' |
70 | + % pformat(self._stock_data)) |
71 | + |
72 | + |
73 | +def make_options(): |
74 | + parser = argparse.ArgumentParser("Test server for libdep-service") |
75 | + parser.add_argument('data', type=argparse.FileType('r'), nargs='?') |
76 | + return parser |
77 | + |
78 | + |
79 | +def run_web_server(reactor, root, port): |
80 | + endpoint = TCP4ServerEndpoint(reactor, port) |
81 | + site = Site(root) |
82 | + return endpoint.listen(site) |
83 | + |
84 | + |
85 | +def unexpected_error(failure): |
86 | + global _EXIT_CODE |
87 | + failure.printTraceback(sys.stderr) |
88 | + mod_reactor.stop() |
89 | + _EXIT_CODE = 2 |
90 | + |
91 | + |
92 | +def get_base_url(listening_port, hostname=None): |
93 | + address = listening_port.getHost() |
94 | + if not hostname: |
95 | + hostname = address.host |
96 | + return 'http://%s:%s' % (hostname, address.port) |
97 | + |
98 | + |
99 | +def run(reactor, data, hostname='localhost', port=0): |
100 | + root = StockResponse(data) |
101 | + d = maybeDeferred(run_web_server, reactor, root, port) |
102 | + |
103 | + def print_port(listening_port): |
104 | + print get_base_url(listening_port, hostname) |
105 | + # Must flush here, so that things watching our stdout know that we've |
106 | + # started. |
107 | + sys.stdout.flush() |
108 | + return listening_port |
109 | + |
110 | + d.addCallback(print_port) |
111 | + d.addErrback(unexpected_error) |
112 | + return d |
113 | + |
114 | + |
115 | +def main(): |
116 | + parser = make_options() |
117 | + args = parser.parse_args() |
118 | + if args.data: |
119 | + data = json.load(args.data) |
120 | + else: |
121 | + data = {} |
122 | + mod_reactor.callWhenRunning(run, mod_reactor, data) |
123 | + mod_reactor.run() |
124 | + sys.exit(_EXIT_CODE) |
125 | |
126 | === modified file 'djlibdep/tests/__init__.py' |
127 | --- djlibdep/tests/__init__.py 2012-09-10 14:34:38 +0000 |
128 | +++ djlibdep/tests/__init__.py 2012-09-28 14:14:21 +0000 |
129 | @@ -20,9 +20,11 @@ |
130 | |
131 | TEST_MODULES = [ |
132 | 'api', |
133 | + 'interface', |
134 | 'pep8', |
135 | 'preflight', |
136 | - 'views' |
137 | + 'test_double', |
138 | + 'views', |
139 | ] |
140 | |
141 | SUITE_FACTORY = OptimisingTestSuite |
142 | |
143 | === added file 'djlibdep/tests/_djangofixture.py' |
144 | --- djlibdep/tests/_djangofixture.py 1970-01-01 00:00:00 +0000 |
145 | +++ djlibdep/tests/_djangofixture.py 2012-09-28 14:14:21 +0000 |
146 | @@ -0,0 +1,86 @@ |
147 | +import errno |
148 | +import subprocess |
149 | +import sys |
150 | +import time |
151 | +from urllib2 import ( |
152 | + URLError, |
153 | + urlopen, |
154 | + ) |
155 | + |
156 | +from fixtures import Fixture |
157 | +from testtools.content import ( |
158 | + Content, |
159 | + UTF8_TEXT, |
160 | + ) |
161 | +from twisted.internet.error import TimeoutError |
162 | + |
163 | + |
164 | +def poll(poll_interval, max_tries, predicate, *args, **kwargs): |
165 | + for i in range(max_tries): |
166 | + if predicate(*args, **kwargs): |
167 | + return |
168 | + time.sleep(poll_interval) |
169 | + raise TimeoutError("Timed out waiting for %r" % (predicate,)) |
170 | + |
171 | + |
172 | +def _is_server_up(url): |
173 | + try: |
174 | + response = urlopen(url) |
175 | + except URLError, e: |
176 | + error_no = getattr(e.reason, 'errno', None) |
177 | + if error_no in (errno.ECONNREFUSED, errno.ECONNRESET): |
178 | + return False |
179 | + raise |
180 | + except IOError, e: |
181 | + if e.errno in (errno.ECONNREFUSED, errno.ECONNRESET): |
182 | + return False |
183 | + raise |
184 | + return response.code == 200 |
185 | + |
186 | + |
187 | +def poll_until_running(url, poll_interval=0.05, max_tries=100): |
188 | + try: |
189 | + poll(poll_interval, max_tries, _is_server_up, url) |
190 | + except TimeoutError: |
191 | + raise TimeoutError("Timed out waiting for %s to come up" % (url,)) |
192 | + |
193 | + |
194 | +def get_manage_location(): |
195 | + return 'django_project/manage.py' |
196 | + |
197 | + |
198 | +# XXX: Copied from lp:pkgme-service. Extract to separate library. |
199 | +class DjangoFixture(Fixture): |
200 | + """A simple Django service, with database. |
201 | + |
202 | + Essentially does 'runserver'. |
203 | + """ |
204 | + |
205 | + def __init__(self, all_clear_path, port=8001): |
206 | + super(DjangoFixture, self).__init__() |
207 | + self._all_clear_path = all_clear_path |
208 | + # XXX: parallelism: Hard-code the port to run on for now. Don't know |
209 | + # how to figure out what port it's actually listening on. |
210 | + self._port = port |
211 | + |
212 | + def setUp(self): |
213 | + super(DjangoFixture, self).setUp() |
214 | + process = subprocess.Popen( |
215 | + [sys.executable, get_manage_location(), |
216 | + 'runserver', '--noreload', str(self._port)], |
217 | + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
218 | + self.addCleanup(process.terminate) |
219 | + self.addCleanup( |
220 | + self.addDetail, |
221 | + 'runserver-log', |
222 | + Content( |
223 | + UTF8_TEXT, |
224 | + process.stdout.readlines)) |
225 | + self.base_url = 'http://localhost:%s' % (self._port,) |
226 | + all_clear_url = '%s/%s' % (self.base_url, self._all_clear_path) |
227 | + try: |
228 | + poll_until_running(all_clear_url) |
229 | + except: |
230 | + # fixtures don't get cleaned up automatically if setUp fails. |
231 | + self.cleanUp() |
232 | + raise |
233 | |
234 | === added file 'djlibdep/tests/test_interface.py' |
235 | --- djlibdep/tests/test_interface.py 1970-01-01 00:00:00 +0000 |
236 | +++ djlibdep/tests/test_interface.py 2012-09-28 14:14:21 +0000 |
237 | @@ -0,0 +1,77 @@ |
238 | +"""Test the HTTP interface of libdep-service.""" |
239 | + |
240 | +import json |
241 | +from urllib2 import urlopen |
242 | + |
243 | +from devportalbinary.database import PackageDatabase |
244 | +from devportalbinary.testing import ( |
245 | + DatabaseConfig, |
246 | + PostgresDatabaseFixture, |
247 | + ) |
248 | +from fixtures import Fixture |
249 | +from testresources import ( |
250 | + FixtureResource, |
251 | + ResourcedTestCase, |
252 | + ) |
253 | +from testscenarios import generate_scenarios |
254 | +from testtools import TestCase |
255 | + |
256 | +from .helpers import populate_sample_data |
257 | +from .test_test_double import ( |
258 | + test_double_fixture, |
259 | + ) |
260 | +from ._djangofixture import DjangoFixture |
261 | + |
262 | + |
263 | +class RealServerFixture(Fixture): |
264 | + |
265 | + def __init__(self, sample_data): |
266 | + super(RealServerFixture, self).__init__() |
267 | + self._sample_data = sample_data |
268 | + |
269 | + def setUp(self): |
270 | + super(RealServerFixture, self).setUp() |
271 | + db_fixture = self.useFixture(PostgresDatabaseFixture()) |
272 | + # This has to come first so the spawned Django server can inherit our |
273 | + # modified environment, and thus our modified configuration. |
274 | + self.useFixture(DatabaseConfig(db_fixture)) |
275 | + django = self.useFixture(DjangoFixture('v1/service_check')) |
276 | + db = PackageDatabase(db_fixture.conn) |
277 | + self.addCleanup(db.close) |
278 | + populate_sample_data(db, self._sample_data) |
279 | + self.base_url = django.base_url |
280 | + |
281 | +real_server_fixture = FixtureResource( |
282 | + RealServerFixture( |
283 | + [('libc', {'i386': {'libc.so.6': 'libc-bin'}}), |
284 | + ])) |
285 | + |
286 | + |
287 | +class InterfaceTests(TestCase, ResourcedTestCase): |
288 | + |
289 | + scenarios = [ |
290 | + ('real', {'resources': [('server', real_server_fixture)]}), |
291 | + ('double', {'resources': [('server', test_double_fixture)]}), |
292 | + ] |
293 | + |
294 | + def test_service_check(self): |
295 | + url = '%s/v1/service_check' % (self.server.base_url,) |
296 | + data = urlopen(url).read() |
297 | + self.assertEqual('Hello world!', data) |
298 | + |
299 | + def test_not_found(self): |
300 | + url = '%s/v1/get_binaries_for_libraries' % (self.server.base_url,) |
301 | + url += '?libs=doesnotexist' |
302 | + data = urlopen(url).read() |
303 | + self.assertEqual('{}', data) |
304 | + |
305 | + def test_found(self): |
306 | + url = '%s/v1/get_binaries_for_libraries' % (self.server.base_url,) |
307 | + url += '?libs=libc.so.6' |
308 | + data = urlopen(url).read() |
309 | + self.assertEqual(json.dumps({'libc.so.6': ['libc-bin']}), data) |
310 | + |
311 | + |
312 | +def load_tests(loader, tests, ignored): |
313 | + from unittest import TestSuite |
314 | + return TestSuite(generate_scenarios(tests)) |
315 | |
316 | === added file 'djlibdep/tests/test_test_double.py' |
317 | --- djlibdep/tests/test_test_double.py 1970-01-01 00:00:00 +0000 |
318 | +++ djlibdep/tests/test_test_double.py 2012-09-28 14:14:21 +0000 |
319 | @@ -0,0 +1,132 @@ |
320 | +import json |
321 | +import os |
322 | +from pprint import pformat |
323 | +import subprocess |
324 | +import sys |
325 | +from urllib2 import urlopen |
326 | + |
327 | +from fixtures import Fixture |
328 | +from testtools import TestCase |
329 | +from testresources import ( |
330 | + FixtureResource, |
331 | + ResourcedTestCase, |
332 | + ) |
333 | +from treeshape import ( |
334 | + CONTENT, |
335 | + FileTree, |
336 | + ) |
337 | +from twisted.web.resource import getChildForRequest |
338 | +from twisted.web.test.test_web import DummyRequest |
339 | + |
340 | +from ..test_double import ( |
341 | + StockResponse, |
342 | + ) |
343 | + |
344 | + |
345 | +def get_test_double_path(): |
346 | + """Get the path to the test double executable.""" |
347 | + # We are /blah/blah/djpkgme/tests/test_test_double.py. We want to get to |
348 | + # /blah/blah/bin/libdep-service-testd. |
349 | + up = os.path.dirname |
350 | + return os.path.join(up(up(up(__file__))), 'bin', 'libdep-service-testd') |
351 | + |
352 | + |
353 | +class LibdepServiceDouble(Fixture): |
354 | + |
355 | + def __init__(self, data): |
356 | + super(LibdepServiceDouble, self).__init__() |
357 | + self._data = data |
358 | + |
359 | + def setUp(self): |
360 | + super(LibdepServiceDouble, self).setUp() |
361 | + tree = self.useFixture( |
362 | + FileTree({'data.json': {CONTENT: json.dumps(self._data)}})) |
363 | + data_path = tree.join('data.json') |
364 | + p = subprocess.Popen( |
365 | + [sys.executable, get_test_double_path(), data_path], |
366 | + stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
367 | + self.addCleanup(p.terminate) |
368 | + # XXX: For better debug-ability. |
369 | + if p.poll() is not None: |
370 | + raise Exception(p.stderr.read()) |
371 | + # XXX: Assume that the "I'm ready" message is only a single line. |
372 | + # XXX: Maybe make it JSON? |
373 | + self.base_url = p.stdout.readline().strip() |
374 | + |
375 | + |
376 | +TEST_DATA = { |
377 | + 'v1/service_check': [({}, 'Hello world!')], |
378 | + 'v1/get_binaries_for_libraries': [ |
379 | + ({'libs': ['doesnotexist']}, '{}'), |
380 | + ({'libs': ['libc.so.6']}, '{"libc.so.6": ["libc-bin"]}'), |
381 | + ], |
382 | + } |
383 | +test_double_fixture = FixtureResource(LibdepServiceDouble(TEST_DATA)) |
384 | + |
385 | + |
386 | +class TestInterface(TestCase, ResourcedTestCase): |
387 | + """Tests for the public interface of the test double. |
388 | + |
389 | + We want any & all clients to be able to use the test double for writing |
390 | + their tests, regardless of the language that they're written in. As such, |
391 | + the interface for the test double is "run a process". These tests very |
392 | + that we can run the process, feed it data, and shut it down. |
393 | + """ |
394 | + |
395 | + resources = [('double', test_double_fixture)] |
396 | + |
397 | + def test_executable(self): |
398 | + # We can find and run the executable sanely. Using '--help' as an |
399 | + # option as that's unlikely to launch a web server and more likely to |
400 | + # terminate quickly! |
401 | + p = subprocess.Popen( |
402 | + [sys.executable, get_test_double_path(), '--help'], |
403 | + stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
404 | + out, err = p.communicate() |
405 | + self.assertEqual(0, p.returncode) |
406 | + |
407 | + def test_launches_web_server(self): |
408 | + fd = urlopen(self.double.base_url) |
409 | + self.assertEqual(200, fd.code) |
410 | + |
411 | + def test_serves_simple_data_from_file(self): |
412 | + service_check_url = '%s/v1/service_check' % (self.double.base_url,) |
413 | + fd = urlopen(service_check_url) |
414 | + self.assertEqual('Hello world!', fd.read()) |
415 | + |
416 | + |
417 | +def _get_path(resource, path, args=None): |
418 | + request = DummyRequest(path) |
419 | + if args: |
420 | + request.args = args |
421 | + child = getChildForRequest(resource, request) |
422 | + return child.render(request) |
423 | + |
424 | + |
425 | +class TestStockResponses(TestCase): |
426 | + |
427 | + def test_simple_resource(self): |
428 | + resource = StockResponse({'foo': [({}, 'bar')]}) |
429 | + result = _get_path(resource, ['foo']) |
430 | + self.assertEqual('bar', result) |
431 | + |
432 | + def test_recursive_resource(self): |
433 | + resource = StockResponse({'foo/bar': [({}, 'baz')]}) |
434 | + result = _get_path(resource, ['foo', 'bar']) |
435 | + self.assertEqual('baz', result) |
436 | + |
437 | + def test_query_params(self): |
438 | + resource = StockResponse({'foo': [({'baz': 'bar'}, 'qux')]}) |
439 | + result = _get_path(resource, ['foo'], {'baz': 'bar'}) |
440 | + self.assertEqual('qux', result) |
441 | + |
442 | + def test_not_found(self): |
443 | + resource = StockResponse({}) |
444 | + result = _get_path(resource, ['foo']) |
445 | + self.assertIn('404', result) |
446 | + |
447 | + def test_base(self): |
448 | + data = {'foo': [({}, 'bar')]} |
449 | + resource = StockResponse(data) |
450 | + result = _get_path(resource, []) |
451 | + self.assertIn(pformat(data), result) |
452 | |
453 | === modified file 'setup.py' |
454 | --- setup.py 2012-08-24 19:34:45 +0000 |
455 | +++ setup.py 2012-09-28 14:14:21 +0000 |
456 | @@ -37,8 +37,14 @@ |
457 | # The 0.4 package of django-openid-auth doesn't depend on |
458 | # python-openid, so we have to list it here. |
459 | 'python-openid>=2.2.5', |
460 | + 'Twisted', |
461 | 'txstatsd', |
462 | ], |
463 | + entry_points = { |
464 | + 'console_scripts': [ |
465 | + 'libdep-service-testd=djlibdep.test_double:main', |
466 | + ], |
467 | + }, |
468 | zip_safe=False, |
469 | packages=find_packages('.'), |
470 | ) |
471 | |
472 | === added directory 'test-double' |
473 | === added file 'test-double/libdep_double.go' |
474 | --- test-double/libdep_double.go 1970-01-01 00:00:00 +0000 |
475 | +++ test-double/libdep_double.go 2012-09-28 14:14:21 +0000 |
476 | @@ -0,0 +1,40 @@ |
477 | +package main |
478 | + |
479 | +import ( |
480 | + "encoding/json" |
481 | + "fmt" |
482 | + "log" |
483 | + "net" |
484 | + "net/http" |
485 | + "os" |
486 | +) |
487 | + |
488 | +type StartupDetails struct { |
489 | + Error string |
490 | + Addr string |
491 | +} |
492 | + |
493 | +func main(){ |
494 | + http.HandleFunc("/", sayHello) |
495 | + details := StartupDetails{} |
496 | + listner, err := net.Listen("tcp", "127.0.0.1:0") |
497 | + if err != nil { |
498 | + details.Error = err.Error() |
499 | + } else { |
500 | + details.Addr = listner.Addr().String() |
501 | + } |
502 | + enc := json.NewEncoder(os.Stdout) |
503 | + err = enc.Encode(details) |
504 | + if err != nil { |
505 | + log.Fatal(err) |
506 | + } |
507 | + err = http.Serve(listner, nil) |
508 | + if err != nil { |
509 | + log.Fatal("Serve:", err) |
510 | + } |
511 | +} |
512 | + |
513 | +func sayHello(w http.ResponseWriter, req *http.Request) { |
514 | + fmt.Printf("New Request\n") |
515 | + fmt.Fprintf(w, "<h1>Go Say's Hello</h1><h2>(Via http)</h2>") |
516 | +} |
517 | |
518 | === modified file 'versions.cfg' |
519 | --- versions.cfg 2012-09-13 22:20:17 +0000 |
520 | +++ versions.cfg 2012-09-28 14:14:21 +0000 |
521 | @@ -38,7 +38,7 @@ |
522 | pep8 = 1.3.3 |
523 | PIL = 1.1.7 |
524 | pkgme = 0.4.1 |
525 | -pkgme-devportal = 0.4.3 |
526 | +pkgme-devportal = 0.4.5 |
527 | postgresfixture = 0.1.2 |
528 | psycopg2 = 2.4.5 |
529 | pyflakes = 0.5.0 |
530 | @@ -50,8 +50,10 @@ |
531 | south = 0.7.3 |
532 | storm = 0.19 |
533 | testresources = 0.2.5 |
534 | +testscenarios = 0.3 |
535 | testtools = 0.9.16 |
536 | treeshape = 0.2.1 |
537 | +Twisted = 12.1.0 |
538 | # A pre-release of txstatsd as there hasn't been a real release yet. |
539 | # Can be replaced with the proper release when it happens. |
540 | # This dist was created from lp:~james-w/txstatsd/pre-release |