Merge lp:~ralsina/ubuntu-sso-client/log-better into lp:ubuntu-sso-client
- log-better
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Natalia Bidart | ||||
Approved revision: | 772 | ||||
Merged at revision: | 764 | ||||
Proposed branch: | lp:~ralsina/ubuntu-sso-client/log-better | ||||
Merge into: | lp:ubuntu-sso-client | ||||
Diff against target: |
413 lines (+347/-5) 7 files modified
ubuntu_sso/logger.py (+2/-3) ubuntu_sso/utils/ui.py (+2/-2) ubuntu_sso/xdg_base_directory/__init__.py (+40/-0) ubuntu_sso/xdg_base_directory/tests/__init__.py (+16/-0) ubuntu_sso/xdg_base_directory/tests/test_common.py (+56/-0) ubuntu_sso/xdg_base_directory/tests/test_windows.py (+112/-0) ubuntu_sso/xdg_base_directory/windows.py (+119/-0) |
||||
To merge this branch: | bzr merge lp:~ralsina/ubuntu-sso-client/log-better | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Natalia Bidart (community) | Approve | ||
Review via email: mp+72573@code.launchpad.net |
Commit message
Move the windows xdg implementation from u1-client to sso so we can use it in all our projects.
Description of the change
Move the windows xdg implementation from u1-client to sso so we can use it in all our projects.
- 767. By Roberto Alsina
-
added the windows-specific tests
- 768. By Roberto Alsina
-
add __init__
- 769. By Roberto Alsina
-
forgot one
Natalia Bidart (nataliabidart) wrote : | # |
I'm getting all these failures when running ./run-tests:
ubuntu_
121: [E1101, get_data_dir] Module 'ubuntu_
ubuntu_
24: [C0103] Invalid name "load_config_paths" (should match (([A-Z_
25: [C0103] Invalid name "save_config_path" (should match (([A-Z_
26: [C0103] Invalid name "xdg_cache_home" (should match (([A-Z_
27: [C0103] Invalid name "xdg_data_home" (should match (([A-Z_
30: [C0103] Invalid name "load_config_paths" (should match (([A-Z_
31: [C0103] Invalid name "save_config_path" (should match (([A-Z_
32: [C0103] Invalid name "xdg_cache_home" (should match (([A-Z_
33: [C0103] Invalid name "xdg_data_home" (should match (([A-Z_
35: [C0103] Invalid name "ubuntuone_log_dir" (should match (([A-Z_
ubuntu_
1: [C0111] Missing docstring
ubuntu_
28: [W0621, get_special_
23: [C0111, get_special_
30: [F0401, get_special_
45: [C0103] Invalid name "special_folders" (should match (([A-Z_
47: [C0103] Invalid name "home_path" (should match (([A-Z_
48: [C0103] Invalid name "app_local_
49: [C0103] Invalid name "app_global_
52: [C0103] Invalid name "xdg_data_home" (should match (([A-Z_
62: [C0103] Invalid name "xdg_data_dirs" (should match (([A-Z_
66: [C0103] Invalid name "xdg_cache_home" (should match (([A-Z_
70: [C0103] Invalid name "xdg_config_home" (should match (([A-Z_
81: [C0103] Invalid name "xdg_config_dirs" (should match (([A-Z_
83: [C0103] Invalid name "xdg_data_dirs" (should match (([A-Z_
84: [C0103] Invalid name "xdg_config_dirs" (should match (([A-Z_
You may wanna disable C0103 in ubuntu_
Natalia Bidart (nataliabidart) wrote : | # |
Another style fix:
* this import from logging.handlers import RotatingFileHandler should be separated by a blank line from the stdlib imports.
- 770. By Roberto Alsina
-
style fix
- 771. By Roberto Alsina
-
Lint fixes
Roberto Alsina (ralsina) wrote : | # |
Pushed with all the lint fixes (and hey, it was missing making xdg_data_dirs visible)
- 772. By Roberto Alsina
-
publish missing member, lint fixes
Natalia Bidart (nataliabidart) wrote : | # |
Looks good!
Preview Diff
1 | === modified file 'ubuntu_sso/logger.py' |
2 | --- ubuntu_sso/logger.py 2011-01-11 19:13:19 +0000 |
3 | +++ ubuntu_sso/logger.py 2011-08-23 15:43:28 +0000 |
4 | @@ -25,12 +25,11 @@ |
5 | import os |
6 | import sys |
7 | |
8 | -import xdg.BaseDirectory |
9 | - |
10 | from logging.handlers import RotatingFileHandler |
11 | |
12 | +from ubuntu_sso import xdg_base_directory |
13 | |
14 | -LOGFOLDER = os.path.join(xdg.BaseDirectory.xdg_cache_home, 'sso') |
15 | +LOGFOLDER = os.path.join(xdg_base_directory.xdg_cache_home, 'sso') |
16 | # create log folder if it doesn't exists |
17 | if not os.path.exists(LOGFOLDER): |
18 | os.makedirs(LOGFOLDER) |
19 | |
20 | === modified file 'ubuntu_sso/utils/ui.py' |
21 | --- ubuntu_sso/utils/ui.py 2011-07-28 22:50:33 +0000 |
22 | +++ ubuntu_sso/utils/ui.py 2011-08-23 15:43:28 +0000 |
23 | @@ -20,10 +20,10 @@ |
24 | |
25 | import os |
26 | import re |
27 | -import xdg |
28 | import gettext |
29 | |
30 | from ubuntu_sso.logger import setup_logging |
31 | +from ubuntu_sso import xdg_base_directory |
32 | |
33 | logger = setup_logging('ubuntu_sso.utils.ui') |
34 | |
35 | @@ -118,7 +118,7 @@ |
36 | return result |
37 | |
38 | # no local data dir, looking within system data dirs |
39 | - data_dirs = xdg.BaseDirectory.xdg_data_dirs |
40 | + data_dirs = xdg_base_directory.xdg_data_dirs |
41 | for path in data_dirs: |
42 | result = os.path.join(path, 'ubuntu-sso-client', 'data') |
43 | result = os.path.abspath(result) |
44 | |
45 | === added directory 'ubuntu_sso/xdg_base_directory' |
46 | === added file 'ubuntu_sso/xdg_base_directory/__init__.py' |
47 | --- ubuntu_sso/xdg_base_directory/__init__.py 1970-01-01 00:00:00 +0000 |
48 | +++ ubuntu_sso/xdg_base_directory/__init__.py 2011-08-23 15:43:28 +0000 |
49 | @@ -0,0 +1,40 @@ |
50 | +# Author: Natalia B. Bidart <natalia.bidart@canonical.com> |
51 | +# |
52 | +# Copyright 2011 Canonical Ltd. |
53 | +# |
54 | +# This program is free software: you can redistribute it and/or modify it |
55 | +# under the terms of the GNU General Public License version 3, as published |
56 | +# by the Free Software Foundation. |
57 | +# |
58 | +# This program is distributed in the hope that it will be useful, but |
59 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
60 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
61 | +# PURPOSE. See the GNU General Public License for more details. |
62 | +# |
63 | +# You should have received a copy of the GNU General Public License along |
64 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
65 | + |
66 | +"""XDG multiplatform.""" |
67 | + |
68 | +import os |
69 | +import sys |
70 | + |
71 | +# pylint: disable=C0103 |
72 | +if sys.platform == "win32": |
73 | + from ubuntu_sso.xdg_base_directory import windows |
74 | + load_config_paths = windows.load_config_paths |
75 | + save_config_path = windows.save_config_path |
76 | + xdg_cache_home = windows.xdg_cache_home |
77 | + xdg_data_home = windows.xdg_data_home |
78 | + xdg_data_dirs = windows.xdg_data_dirs |
79 | +else: |
80 | + import xdg.BaseDirectory |
81 | + load_config_paths = xdg.BaseDirectory.load_config_paths |
82 | + save_config_path = xdg.BaseDirectory.save_config_path |
83 | + xdg_cache_home = xdg.BaseDirectory.xdg_cache_home |
84 | + xdg_data_home = xdg.BaseDirectory.xdg_data_home |
85 | + xdg_data_dirs = xdg.BaseDirectory.xdg_data_dirs |
86 | + |
87 | +ubuntuone_log_dir = os.path.join(xdg_cache_home, 'ubuntuone', 'log') |
88 | +if not os.path.exists(ubuntuone_log_dir): |
89 | + os.makedirs(ubuntuone_log_dir) |
90 | |
91 | === added directory 'ubuntu_sso/xdg_base_directory/tests' |
92 | === added file 'ubuntu_sso/xdg_base_directory/tests/__init__.py' |
93 | --- ubuntu_sso/xdg_base_directory/tests/__init__.py 1970-01-01 00:00:00 +0000 |
94 | +++ ubuntu_sso/xdg_base_directory/tests/__init__.py 2011-08-23 15:43:28 +0000 |
95 | @@ -0,0 +1,16 @@ |
96 | +# ubuntu_sso - Ubuntu Single Sign On client support for desktop apps |
97 | +# |
98 | +# Copyright 2009-2010 Canonical Ltd. |
99 | +# |
100 | +# This program is free software: you can redistribute it and/or modify it |
101 | +# under the terms of the GNU General Public License version 3, as published |
102 | +# by the Free Software Foundation. |
103 | +# |
104 | +# This program is distributed in the hope that it will be useful, but |
105 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
106 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
107 | +# PURPOSE. See the GNU General Public License for more details. |
108 | +# |
109 | +# You should have received a copy of the GNU General Public License along |
110 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
111 | +"""XDG tests.""" |
112 | |
113 | === added file 'ubuntu_sso/xdg_base_directory/tests/test_common.py' |
114 | --- ubuntu_sso/xdg_base_directory/tests/test_common.py 1970-01-01 00:00:00 +0000 |
115 | +++ ubuntu_sso/xdg_base_directory/tests/test_common.py 2011-08-23 15:43:28 +0000 |
116 | @@ -0,0 +1,56 @@ |
117 | +# -*- coding: utf-8 -*- |
118 | +# |
119 | +# Authors: Natalia B. Bidart <natalia.bidart@canonical.com> |
120 | +# |
121 | +# Copyright 2011 Canonical Ltd. |
122 | +# |
123 | +# This program is free software: you can redistribute it and/or modify it |
124 | +# under the terms of the GNU General Public License version 3, as published |
125 | +# by the Free Software Foundation. |
126 | +# |
127 | +# This program is distributed in the hope that it will be useful, but |
128 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
129 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
130 | +# PURPOSE. See the GNU General Public License for more details. |
131 | +# |
132 | +# You should have received a copy of the GNU General Public License along |
133 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
134 | + |
135 | +"""Platform independent tests for the XDG constants.""" |
136 | + |
137 | +import os |
138 | + |
139 | +from twisted.trial.unittest import TestCase |
140 | + |
141 | +from ubuntu_sso import xdg_base_directory |
142 | + |
143 | + |
144 | +class TestBaseDirectory(TestCase): |
145 | + """Tests for the BaseDirectory module.""" |
146 | + |
147 | + def test_ubuntuone_log_dir(self): |
148 | + """The ubuntuone_log_dir is correct.""" |
149 | + expected = os.path.join(xdg_base_directory.xdg_cache_home, |
150 | + 'ubuntuone', 'log') |
151 | + self.assertEqual(expected, xdg_base_directory.ubuntuone_log_dir) |
152 | + self.assertTrue(os.path.exists(expected)) |
153 | + |
154 | + def test_xdg_cache_home_is_bytes(self): |
155 | + """The returned path is bytes.""" |
156 | + actual = xdg_base_directory.xdg_cache_home |
157 | + self.assertIsInstance(actual, str) |
158 | + |
159 | + def test_xdg_data_home_is_bytes(self): |
160 | + """The returned path is bytes.""" |
161 | + actual = xdg_base_directory.xdg_data_home |
162 | + self.assertIsInstance(actual, str) |
163 | + |
164 | + def test_load_config_paths_filter(self): |
165 | + """Since those folders don't exist, this should be empty.""" |
166 | + self.assertEqual(list(xdg_base_directory.load_config_paths("x")), []) |
167 | + |
168 | + def test_save_config_path(self): |
169 | + """The path should end with xdg_config/x (respecting the separator).""" |
170 | + self.patch(os, "makedirs", lambda *args: None) |
171 | + result = xdg_base_directory.save_config_path("x") |
172 | + self.assertEqual(result.split(os.sep)[-2:], ['xdg_config', 'x']) |
173 | |
174 | === added file 'ubuntu_sso/xdg_base_directory/tests/test_windows.py' |
175 | --- ubuntu_sso/xdg_base_directory/tests/test_windows.py 1970-01-01 00:00:00 +0000 |
176 | +++ ubuntu_sso/xdg_base_directory/tests/test_windows.py 2011-08-23 15:43:28 +0000 |
177 | @@ -0,0 +1,112 @@ |
178 | +# -*- coding: utf-8 -*- |
179 | +# |
180 | +# Authors: Natalia B. Bidart <natalia.bidart@canonical.com> |
181 | +# |
182 | +# Copyright 2011 Canonical Ltd. |
183 | +# |
184 | +# This program is free software: you can redistribute it and/or modify it |
185 | +# under the terms of the GNU General Public License version 3, as published |
186 | +# by the Free Software Foundation. |
187 | +# |
188 | +# This program is distributed in the hope that it will be useful, but |
189 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
190 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
191 | +# PURPOSE. See the GNU General Public License for more details. |
192 | +# |
193 | +# You should have received a copy of the GNU General Public License along |
194 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
195 | + |
196 | +"""Windows-specific tests for the XDG constants.""" |
197 | + |
198 | +import os |
199 | +import sys |
200 | + |
201 | +from twisted.trial.unittest import TestCase |
202 | +from ubuntuone.devtools.testcase import skipIfOS |
203 | + |
204 | + |
205 | +# pylint: disable=E1101, E0611, F0401 |
206 | +if sys.platform == "win32": |
207 | + import win32com.shell |
208 | + from ubuntu_sso.xdg_base_directory.windows import ( |
209 | + get_config_dirs, |
210 | + get_data_dirs, |
211 | + get_special_folders, |
212 | + ) |
213 | + |
214 | + |
215 | +# pylint: disable=C0103 |
216 | +class FakeShellConModule(object): |
217 | + """Override CSIDL_ constants.""" |
218 | + CSIDL_PROFILE = 0 |
219 | + CSIDL_LOCAL_APPDATA = 1 |
220 | + CSIDL_COMMON_APPDATA = 2 |
221 | + |
222 | + |
223 | +class FakeShellModule(object): |
224 | + |
225 | + """Fake Shell Module.""" |
226 | + |
227 | + def __init__(self): |
228 | + """Set the proper mapping between CSIDL_ consts.""" |
229 | + self.values = { |
230 | + 0: u'c:\\path\\to\\users\\home', |
231 | + 1: u'c:\\path\\to\\users\\home\\appData\\local', |
232 | + 2: u'c:\\programData', |
233 | + } |
234 | + |
235 | + def SHGetFolderPath(self, dummy0, shellconValue, dummy2, dummy3): |
236 | + """Override SHGetFolderPath functionality.""" |
237 | + return self.values[shellconValue] |
238 | + |
239 | + |
240 | +@skipIfOS('linux2', 'Windows-specific tests.') |
241 | +class TestBaseDirectoryWindows(TestCase): |
242 | + """Tests for the BaseDirectory module.""" |
243 | + |
244 | + def test_get_special_folders(self): |
245 | + """Make sure we can import the platform module.""" |
246 | + |
247 | + shellModule = FakeShellModule() |
248 | + self.patch(win32com.shell, "shell", shellModule) |
249 | + self.patch(win32com.shell, "shellcon", FakeShellConModule()) |
250 | + special_folders = get_special_folders() |
251 | + self.assertTrue('Personal' in special_folders) |
252 | + self.assertTrue('Local AppData' in special_folders) |
253 | + self.assertTrue('AppData' in special_folders) |
254 | + self.assertTrue('Common AppData' in special_folders) |
255 | + |
256 | + self.assertTrue(special_folders['Personal'] == \ |
257 | + shellModule.values[FakeShellConModule.CSIDL_PROFILE]) |
258 | + self.assertTrue(special_folders['Local AppData'] == \ |
259 | + shellModule.values[FakeShellConModule.CSIDL_LOCAL_APPDATA]) |
260 | + self.assertTrue(special_folders['Local AppData'].startswith( |
261 | + special_folders['AppData'])) |
262 | + self.assertTrue(special_folders['Common AppData'] == \ |
263 | + shellModule.values[FakeShellConModule.CSIDL_COMMON_APPDATA]) |
264 | + |
265 | + # Can't use assert_syncdaemon_path |
266 | + for val in special_folders.itervalues(): |
267 | + self.assertIsInstance(val, str) |
268 | + val.decode('utf8') |
269 | + # Should not raise exceptions |
270 | + |
271 | + def test_get_data_dirs(self): |
272 | + """Check thet get_data_dirs uses pathsep correctly.""" |
273 | + bad_sep = filter(lambda x: x not in os.pathsep, ":;") |
274 | + dir_list = ["A", "B", bad_sep, "C"] |
275 | + self.patch(os, "environ", |
276 | + dict(XDG_DATA_DIRS=os.pathsep.join( |
277 | + dir_list))) |
278 | + dirs = get_data_dirs() |
279 | + self.assertEqual(dirs, dir_list) |
280 | + |
281 | + def test_get_config_dirs(self): |
282 | + """Check thet get_data_dirs uses pathsep correctly.""" |
283 | + bad_sep = filter(lambda x: x not in os.pathsep, ":;") |
284 | + dir_list = ["A", "B", bad_sep, "C"] |
285 | + self.patch(os, "environ", |
286 | + dict(XDG_CONFIG_DIRS=os.pathsep.join( |
287 | + dir_list))) |
288 | + dirs = get_config_dirs()[1:] |
289 | + self.assertEqual(dirs, dir_list) |
290 | |
291 | === added file 'ubuntu_sso/xdg_base_directory/windows.py' |
292 | --- ubuntu_sso/xdg_base_directory/windows.py 1970-01-01 00:00:00 +0000 |
293 | +++ ubuntu_sso/xdg_base_directory/windows.py 2011-08-23 15:43:28 +0000 |
294 | @@ -0,0 +1,119 @@ |
295 | +# Authors: Manuel de la Pena <manuel@canonical.com> |
296 | +# Diego Sarmentero <diego.sarmentero@canonical.com> |
297 | +# |
298 | +# Copyright 2011 Canonical Ltd. |
299 | +# |
300 | +# This program is free software: you can redistribute it and/or modify it |
301 | +# under the terms of the GNU General Public License version 3, as published |
302 | +# by the Free Software Foundation. |
303 | +# |
304 | +# This program is distributed in the hope that it will be useful, but |
305 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
306 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
307 | +# PURPOSE. See the GNU General Public License for more details. |
308 | +# |
309 | +# You should have received a copy of the GNU General Public License along |
310 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
311 | + |
312 | +"""XDG helpers for windows.""" |
313 | + |
314 | +import os |
315 | + |
316 | + |
317 | +# pylint: disable=C0103 |
318 | +def get_special_folders(): |
319 | + """ Routine to grab all the Windows Special Folders locations. |
320 | + |
321 | + If successful, returns dictionary |
322 | + of shell folder locations indexed on Windows keyword for each; |
323 | + otherwise, returns an empty dictionary. |
324 | + """ |
325 | + # pylint: disable=W0621, F0401, E0611 |
326 | + special_folders = {} |
327 | + |
328 | + from win32com.shell import shell, shellcon |
329 | + # CSIDL_LOCAL_APPDATA = C:\Users\<username>\AppData\Local |
330 | + # CSIDL_PROFILE = C:\Users\<username> |
331 | + # CSIDL_COMMON_APPDATA = C:\ProgramData |
332 | + # More information on these at |
333 | + # http://msdn.microsoft.com/en-us/library/bb762494(v=vs.85).aspx |
334 | + get_path = lambda name: shell.SHGetFolderPath( |
335 | + 0, getattr(shellcon, name), None, 0).encode('utf8') |
336 | + special_folders['Personal'] = get_path("CSIDL_PROFILE") |
337 | + special_folders['Local AppData'] = get_path("CSIDL_LOCAL_APPDATA") |
338 | + special_folders['AppData'] = os.path.dirname( |
339 | + special_folders['Local AppData']) |
340 | + special_folders['Common AppData'] = get_path("CSIDL_COMMON_APPDATA") |
341 | + return special_folders |
342 | + |
343 | +special_folders = get_special_folders() |
344 | + |
345 | +home_path = special_folders['Personal'] |
346 | +app_local_data_path = special_folders['Local AppData'] |
347 | +app_global_data_path = special_folders['Common AppData'] |
348 | + |
349 | +# use the non roaming app data |
350 | +xdg_data_home = os.environ.get('XDG_DATA_HOME', |
351 | + os.path.join(app_local_data_path, 'xdg')) |
352 | + |
353 | + |
354 | +def get_data_dirs(): |
355 | + """Returns XDG data directories.""" |
356 | + return os.environ.get('XDG_DATA_DIRS', |
357 | + '{0}{1}{2}'.format(app_local_data_path, os.pathsep, |
358 | + app_global_data_path)).split(os.pathsep) |
359 | + |
360 | +xdg_data_dirs = get_data_dirs() |
361 | + |
362 | +# we will return the roaming data wich is as close as we get in windows |
363 | +# regarding caching. |
364 | +xdg_cache_home = os.environ.get('XDG_CACHE_HOME', |
365 | + os.path.join(xdg_data_home, 'cache')) |
366 | + |
367 | +# point to the not roaming app data for the user |
368 | +xdg_config_home = os.environ.get('XDG_CONFIG_HOME', |
369 | + app_local_data_path) |
370 | + |
371 | + |
372 | +def get_config_dirs(): |
373 | + """Return XDG config directories.""" |
374 | + return [xdg_config_home] + \ |
375 | + os.environ.get('XDG_CONFIG_DIRS', |
376 | + app_global_data_path, |
377 | + ).split(os.pathsep) |
378 | + |
379 | +xdg_config_dirs = get_config_dirs() |
380 | + |
381 | +xdg_data_dirs = filter(lambda x: x, xdg_data_dirs) |
382 | +xdg_config_dirs = filter(lambda x: x, xdg_config_dirs) |
383 | + |
384 | + |
385 | +def load_config_paths(*resource): |
386 | + """Iterator of configuration paths. |
387 | + |
388 | + Return an iterator which gives each directory named 'resource' in |
389 | + the configuration search path. Information provided by earlier |
390 | + directories should take precedence over later ones (ie, the user's |
391 | + config dir comes first). |
392 | + """ |
393 | + resource = os.path.join(*resource) |
394 | + for config_dir in xdg_config_dirs: |
395 | + path = os.path.join(config_dir, resource) |
396 | + if os.path.exists(path): |
397 | + yield path |
398 | + |
399 | + |
400 | +def save_config_path(*resource): |
401 | + """Path to save configuration. |
402 | + |
403 | + Ensure $XDG_CONFIG_HOME/<resource>/ exists, and return its path. |
404 | + 'resource' should normally be the name of your application. Use this |
405 | + when SAVING configuration settings. Use the xdg_config_dirs variable |
406 | + for loading. |
407 | + """ |
408 | + resource = os.path.join(*resource) |
409 | + assert not resource.startswith('/') |
410 | + path = os.path.join(xdg_config_home, resource) |
411 | + if not os.path.isdir(path): |
412 | + os.makedirs(path, 0700) |
413 | + return path |
So, since we're adding a new multiplatform module, we need to come up with something like this:
ubuntu_sso/ base_directory/
__ init__. py
test_ common. py <- multiplatform tests
test_ windows. py
main/
foo/
xdg_
__init__.py
windows.py
tests/