1import grp
2import mock_flimflam
3import os
4import pwd
5import stat
6import time
7import utils
8
9from autotest_lib.client.bin import test
10from autotest_lib.client.common_lib import error
11
12class network_ShillInitScripts(test.test):
13    """ Test that shill init scripts perform as expected.  Use the
14        real filesystem (doing a best effort to archive and restore
15        current state).  The shill manager is stopped and a proxy
16        DBus entity is installed to accept DBus messages that are sent
17        via "dbus-send" in the shill startup scripts.  However, the
18        "real" shill is still also started from time to time and we
19        check that it is run with the right command line arguments.
20    """
21    version = 1
22    save_directories = [ '/var/cache/shill',
23                         '/var/cache/flimflam',
24                         '/var/run/shill',
25                         '/var/run/state/logged-in',
26                         '/var/run/dhcpcd',
27                         '/var/lib/dhcpcd',
28                         '/home/chronos/.disable_shill' ]
29    fake_user = 'not-a-real-user@chromium.org'
30    saved_config = '/tmp/network_ShillInitScripts_saved_config.tgz'
31    cryptohome_path_command = 'cryptohome-path'
32    guest_shill_user_profile_dir = '/var/run/shill/guest_user_profile/shill'
33    guest_shill_user_log_dir = '/var/run/shill/guest_user_profile/shill_logs'
34    magic_header = '# --- shill init file test magic header ---'
35
36
37    def start_shill(self):
38        """ Starts a shill instance. """
39        utils.system('start shill')
40
41
42    def stop_shill(self):
43        """ Halt the running shill instance. """
44        utils.system('stop shill', ignore_status=True)
45
46        for attempt in range(10):
47            if not self.find_pid('shill'):
48                break
49            time.sleep(1)
50        else:
51            raise error.TestFail('Shill process does not appear to be dying')
52
53
54    def login(self, user=None):
55        """ Simulate the login process.
56
57        Note: "start" blocks until the "script" block completes.
58
59        @param user string user name (email address) to log in.
60
61        """
62        utils.system('start shill-start-user-session CHROMEOS_USER=%s' %
63                     (user or self.fake_user))
64
65
66    def login_guest(self):
67        """ Simulate guest login.
68
69        For guest login, session-manager passes an empty CHROMEOS_USER arg.
70
71        """
72        self.login('""')
73
74
75    def logout(self):
76        """ Simulate user logout.
77
78        Note: "start" blocks until the "script" block completes.
79
80        """
81        utils.system('start shill-stop-user-session')
82
83
84    def start_test(self):
85        """ Setup the start of the test.  Stop shill and create test harness."""
86        # Stop a system process on test duts for keeping connectivity up.
87        ret = utils.system('stop recover_duts', ignore_status=True)
88        self.recover_duts_stopped = (ret == 0);
89
90        self.stop_shill()
91
92        # Deduce the root cryptohome directory name for our fake user.
93        self.root_cryptohome_dir = utils.system_output(
94            '%s system %s' % (self.cryptohome_path_command, self.fake_user))
95
96        # Deduce the user cryptohome directory name for our fake user.
97        self.user_cryptohome_dir = utils.system_output(
98            '%s user %s' % (self.cryptohome_path_command, self.fake_user))
99
100        # Deduce the directory for memory log storage.
101        self.user_cryptohome_log_dir = ('%s/shill_logs' %
102                                        self.root_cryptohome_dir)
103
104        # The sanitized hash of the username is the basename of the cryptohome.
105        self.fake_user_hash = os.path.basename(self.root_cryptohome_dir)
106
107        # Just in case this hash actually exists, add these to the list of
108        # saved directories.
109        self.save_directories.append(self.root_cryptohome_dir)
110        self.save_directories.append(self.user_cryptohome_dir)
111
112        # Archive the system state we will be modifying, then remove them.
113        utils.system('tar zcvf %s --directory / --ignore-failed-read %s'
114                     ' 2>/dev/null' %
115                     (self.saved_config, ' '.join(self.save_directories)))
116        utils.system('rm -rf %s' % ' '.join(self.save_directories),
117                     ignore_status=True)
118
119        # Create the fake user's system cryptohome directory.
120        os.mkdir(self.root_cryptohome_dir)
121        self.new_shill_user_profile_dir = ('%s/shill' %
122                                           self.root_cryptohome_dir)
123        self.new_shill_user_profile = ('%s/shill.profile' %
124                                       self.new_shill_user_profile_dir)
125
126        # Create the fake user's user cryptohome directory.
127        os.mkdir(self.user_cryptohome_dir)
128        self.flimflam_user_profile_dir = ('%s/flimflam' %
129                                          self.user_cryptohome_dir)
130        self.flimflam_user_profile = ('%s/flimflam.profile' %
131                                      self.flimflam_user_profile_dir)
132        self.old_shill_user_profile_dir = ('%s/shill' %
133                                           self.user_cryptohome_dir)
134        self.old_shill_user_profile = ('%s/shill.profile' %
135                                       self.old_shill_user_profile_dir)
136        self.mock_flimflam = None
137
138
139    def start_mock_flimflam(self):
140        """ Start a mock flimflam instance to accept and log DBus calls. """
141        self.mock_flimflam = mock_flimflam.MockFlimflam()
142        self.mock_flimflam.start()
143
144
145    def erase_state(self):
146        """ Remove all the test harness files. """
147        utils.system('rm -rf %s' % ' '.join(self.save_directories))
148        os.mkdir(self.root_cryptohome_dir)
149        os.mkdir(self.user_cryptohome_dir)
150
151
152    def end_test(self):
153        """ Perform cleanup at the end of the test. """
154        if self.mock_flimflam:
155            self.mock_flimflam.quit()
156            self.mock_flimflam.join()
157        self.erase_state()
158        utils.system('tar zxvf %s --directory /' % self.saved_config)
159        utils.system('rm -f %s' % self.saved_config)
160        self.restart_system_processes()
161
162
163    def restart_system_processes(self):
164        """ Restart vital system services at the end of the test. """
165        utils.system('start shill', ignore_status=True)
166        if self.recover_duts_stopped:
167            utils.system('start recover_duts', ignore_status=True)
168
169
170    def assure(self, must_be_true, assertion_name):
171        """ Perform a named assertion.
172
173        @param must_be_true boolean parameter that must be true.
174        @param assertion_name string name of this assertion.
175
176        """
177        if not must_be_true:
178            raise error.TestFail('%s: Assertion failed: %s' %
179                                 (self.test_name, assertion_name))
180
181
182    def assure_path_owner(self, path, owner):
183        """ Assert that |path| is owned by |owner|.
184
185        @param path string pathname to test.
186        @param owner string user name that should own |path|.
187
188        """
189        self.assure(pwd.getpwuid(os.stat(path).st_uid)[0] == owner,
190                    'Path %s is owned by %s' % (path, owner))
191
192
193    def assure_path_group(self, path, group):
194        """ Assert that |path| is owned by |group|.
195
196        @param path string pathname to test.
197        @param group string group name that should own |path|.
198
199        """
200        self.assure(grp.getgrgid(os.stat(path).st_gid)[0] == group,
201                    'Path %s is group-owned by %s' % (path, group))
202
203
204    def assure_exists(self, path, path_friendly_name):
205        """ Assert that |path| exists.
206
207        @param path string pathname to test.
208        @param path_friendly_name string user-parsable description of |path|.
209
210        """
211        self.assure(os.path.exists(path), '%s exists' % path_friendly_name)
212
213
214    def assure_is_dir(self, path, path_friendly_name):
215        """ Assert that |path| is a directory.
216
217        @param path string pathname to test.
218        @param path_friendly_name string user-parsable description of |path|.
219
220        """
221        self.assure_exists(path, path_friendly_name)
222        self.assure(stat.S_ISDIR(os.lstat(path).st_mode),
223                    '%s is a directory' % path_friendly_name)
224
225
226    def assure_is_link(self, path, path_friendly_name):
227        """ Assert that |path| is a symbolic link.
228
229        @param path string pathname to test.
230        @param path_friendly_name string user-parsable description of |path|.
231
232        """
233        self.assure_exists(path, path_friendly_name)
234        self.assure(stat.S_ISLNK(os.lstat(path).st_mode),
235                    '%s is a symbolic link' % path_friendly_name)
236
237
238    def assure_is_link_to(self, path, pointee, path_friendly_name):
239        """ Assert that |path| is a symbolic link to |pointee|.
240
241        @param path string pathname to test.
242        @param pointee string pathname that |path| should point to.
243        @param path_friendly_name string user-parsable description of |path|.
244
245        """
246        self.assure_is_link(path, path_friendly_name)
247        self.assure(os.readlink(path) == pointee,
248                    '%s is a symbolic link to %s' %
249                    (path_friendly_name, pointee))
250
251
252    def assure_method_calls(self, expected_method_calls, assertion_name):
253        """ Assert that |expected_method_calls| were executed on mock_flimflam.
254
255        @param expected_method_calls list of string-tuple pairs of method
256            name + tuple of arguments.
257        @param assertion_name string name to assign to the assertion.
258
259        """
260        method_calls = self.mock_flimflam.get_method_calls()
261        if len(expected_method_calls) != len(method_calls):
262            self.assure(False, '%s: method call count does not match' %
263                        assertion_name)
264        for expected, actual in zip(expected_method_calls, method_calls):
265            self.assure(actual.method == expected[0],
266                        '%s: method %s matches expected %s' %
267                        (assertion_name, actual.method, expected[0]))
268            self.assure(actual.argument == expected[1],
269                        '%s: argument %s matches expected %s' %
270                        (assertion_name, actual.argument, expected[1]))
271
272
273    def create_file_with_contents(self, filename, contents):
274        """ Create a file named |filename| that contains |contents|.
275
276        @param filename string name of file.
277        @param contents string contents of file.
278
279        """
280        with open(filename, 'w') as f:
281            f.write(contents)
282
283
284    def touch(self, filename):
285        """ Create an empty file named |filename|.
286
287        @param filename string name of file.
288
289        """
290        self.create_file_with_contents(filename, '')
291
292
293    def create_new_shill_user_profile(self, contents):
294        """ Create a fake new user profile with |contents|.
295
296        @param contents string contents of the new user profile.
297
298        """
299        os.mkdir(self.new_shill_user_profile_dir)
300        self.create_file_with_contents(self.new_shill_user_profile, contents)
301
302
303    def create_old_shill_user_profile(self, contents):
304        """ Create a fake old-style user profile with |contents|.
305
306        @param contents string contents of the old user profile.
307
308        """
309        os.mkdir(self.old_shill_user_profile_dir)
310        self.create_file_with_contents(self.old_shill_user_profile, contents)
311
312
313    def create_flimflam_user_profile(self, contents):
314        """ Create a legacy flimflam user profile with |contents|.
315
316        @param contents string contents of the flimflam user profile.
317
318        """
319        os.mkdir(self.flimflam_user_profile_dir)
320        self.create_file_with_contents(self.flimflam_user_profile, contents)
321
322
323    def file_contents(self, filename):
324        """ Returns the contents of |filename|.
325
326        @param filename string name of file to read.
327
328        """
329        with open(filename) as f:
330            return f.read()
331
332
333    def find_pid(self, process_name):
334        """ Returns the process id of |process_name|.
335
336        @param process_name string name of process to search for.
337
338        """
339        return utils.system_output('pgrep %s' % process_name,
340                                   ignore_status=True).split('\n')[0]
341
342
343    def get_commandline(self):
344        """ Returns the command line of the current shill executable. """
345        pid = self.find_pid('shill')
346        return file('/proc/%s/cmdline' % pid).read().split('\0')
347
348
349    def run_once(self):
350        """ Main test loop. """
351        try:
352            self.start_test()
353        except:
354            self.restart_system_processes()
355            raise
356
357        try:
358            self.run_tests([
359                self.test_start_shill,
360                self.test_start_logged_in,
361                self.test_start_port_flimflam_profile])
362
363            # The tests above run a real instance of shill, whereas the tests
364            # below rely on a mock instance of shill.  We must take care not
365            # to run the mock at the same time as a real shill instance.
366            self.start_mock_flimflam()
367
368            self.run_tests([
369                self.test_login,
370                self.test_login_guest,
371                self.test_login_profile_exists,
372                self.test_login_old_shill_profile,
373                self.test_login_invalid_old_shill_profile,
374                self.test_login_ignore_old_shill_profile,
375                self.test_login_flimflam_profile,
376                self.test_login_ignore_flimflam_profile,
377                self.test_login_prefer_old_shill_profile,
378                self.test_login_multi_profile,
379                self.test_logout])
380        finally:
381            # Stop any shill instances started during testing.
382            self.stop_shill()
383            self.end_test()
384
385
386    def run_tests(self, tests):
387        """ Executes each of the test subparts in sequence.
388
389        @param tests list of methods to run.
390
391        """
392        for test in tests:
393          self.test_name = test.__name__
394          test()
395          self.stop_shill()
396          self.erase_state()
397
398
399    def test_start_shill(self):
400        """ Test all created pathnames during shill startup.
401
402        Also ensure the push argument is not provided by default.
403
404        """
405        self.touch('/home/chronos/.disable_shill')
406        self.start_shill()
407        self.assure_is_dir('/var/run/shill', 'Shill run directory')
408        self.assure_is_dir('/var/lib/dhcpcd', 'dhcpcd lib directory')
409        self.assure_path_owner('/var/lib/dhcpcd', 'dhcp')
410        self.assure_path_group('/var/lib/dhcpcd', 'dhcp')
411        self.assure_is_dir('/var/run/dhcpcd', 'dhcpcd run directory')
412        self.assure_path_owner('/var/run/dhcpcd', 'dhcp')
413        self.assure_path_group('/var/run/dhcpcd', 'dhcp')
414        self.assure(not os.path.exists('/home/chronos/.disable_shill'),
415                    'Shill disable file does not exist')
416        self.assure('--push=~chronos/shill' not in self.get_commandline(),
417                    'Shill command line does not contain push argument')
418
419
420    def test_start_logged_in(self):
421        """ Tests starting up shill while a user is already logged in.
422
423        The "--push" argument should not be added even though shill is started
424        while a user is logged in.
425
426        """
427        os.mkdir('/var/run/shill')
428        os.mkdir('/var/run/shill/user_profiles')
429        self.create_new_shill_user_profile('')
430        os.symlink(self.new_shill_user_profile_dir,
431                   '/var/run/shill/user_profiles/chronos')
432        self.touch('/var/run/state/logged-in')
433        self.start_shill()
434        self.assure('--push=~chronos/shill' not in self.get_commandline(),
435                    'Shill command line does not contain push argument')
436        os.unlink('/var/run/state/logged-in')
437
438
439    def test_start_port_flimflam_profile(self):
440        """ Test that we can port a flimflam profile to a new shill profile.
441
442        Startup should move an old flimflam profile into place if a shill
443        profile does not already exist.
444
445        """
446        os.mkdir('/var/cache/flimflam')
447        flimflam_profile = '/var/cache/flimflam/default.profile'
448        self.create_file_with_contents(flimflam_profile, self.magic_header)
449        shill_profile = '/var/cache/shill/default.profile'
450        self.start_shill()
451        self.assure(not os.path.exists(flimflam_profile),
452                    'Flimflam profile no longer exists')
453        self.assure(os.path.exists(shill_profile),
454                    'Shill profile exists')
455        self.assure(self.magic_header in self.file_contents(shill_profile),
456                    'Shill default profile contains our magic header')
457
458
459    def test_start_ignore_flimflam_profile(self):
460        """ Test that we ignore a flimflam profile if a new profile exists.
461
462        Startup should ignore an old flimflam profile if a shill profile
463        already exists.
464
465        """
466        os.mkdir('/var/cache/flimflam')
467        os.mkdir('/var/cache/shill')
468        flimflam_profile = '/var/cache/flimflam/default.profile'
469        self.create_file_with_contents(flimflam_profile, self.magic_header)
470        shill_profile = '/var/cache/shill/default.profile'
471        self.touch(shill_profile)
472        self.start_shill()
473        self.assure(os.path.exists(flimflam_profile),
474                    'Flimflam profile still exists')
475        self.assure(self.magic_header not in self.file_contents(shill_profile),
476                    'Shill default profile does not contain our magic header')
477
478
479    def test_login(self):
480        """ Test the login process.
481
482        Login should create a profile directory, then create and push
483        a user profile, given no previous state.
484
485        """
486        os.mkdir('/var/run/shill')
487        self.login()
488        self.assure(not os.path.exists(self.flimflam_user_profile),
489                    'Flimflam user profile does not exist')
490        self.assure(not os.path.exists(self.old_shill_user_profile),
491                    'Old shill user profile does not exist')
492        self.assure(not os.path.exists(self.new_shill_user_profile),
493                    'New shill user profile does not exist')
494        # The DBus "CreateProfile" method should have been handled
495        # by our mock_flimflam instance, so the profile directory
496        # should not have actually been created.
497        self.assure_is_dir(self.new_shill_user_profile_dir,
498                           'New shill user profile directory')
499        self.assure_is_dir('/var/run/shill/user_profiles',
500                           'Shill profile root')
501        self.assure_is_link_to('/var/run/shill/user_profiles/chronos',
502                               self.new_shill_user_profile_dir,
503                               'Shill profile link')
504        self.assure_is_dir(self.user_cryptohome_log_dir,
505                           'shill user log directory')
506        self.assure_is_link_to('/var/run/shill/log',
507                               self.user_cryptohome_log_dir,
508                               'Shill logs link')
509        self.assure_method_calls([[ 'CreateProfile', '~chronos/shill' ],
510                                  [ 'InsertUserProfile',
511                                    ('~chronos/shill', self.fake_user_hash) ]],
512                                 'CreateProfile and InsertUserProfile '
513                                 'are called')
514
515
516    def test_login_guest(self):
517        """ Tests the guest login process.
518
519        Login should create a temporary profile directory in /var/run,
520        instead of using one within the root directory for normal users.
521
522        """
523        os.mkdir('/var/run/shill')
524        self.login_guest()
525        self.assure(not os.path.exists(self.flimflam_user_profile),
526                    'Flimflam user profile does not exist')
527        self.assure(not os.path.exists(self.old_shill_user_profile),
528                    'Old shill user profile does not exist')
529        self.assure(not os.path.exists(self.new_shill_user_profile),
530                    'New shill user profile does not exist')
531        self.assure(not os.path.exists(self.new_shill_user_profile_dir),
532                    'New shill user profile directory')
533        self.assure_is_dir(self.guest_shill_user_profile_dir,
534                           'shill guest user profile directory')
535        self.assure_is_dir('/var/run/shill/user_profiles',
536                           'Shill profile root')
537        self.assure_is_link_to('/var/run/shill/user_profiles/chronos',
538                               self.guest_shill_user_profile_dir,
539                               'Shill profile link')
540        self.assure_is_dir(self.guest_shill_user_log_dir,
541                           'shill guest user log directory')
542        self.assure_is_link_to('/var/run/shill/log',
543                               self.guest_shill_user_log_dir,
544                               'Shill logs link')
545        self.assure_method_calls([[ 'CreateProfile', '~chronos/shill' ],
546                                  [ 'InsertUserProfile',
547                                    ('~chronos/shill', '') ]],
548                                 'CreateProfile and InsertUserProfile '
549                                 'are called')
550
551
552    def test_login_profile_exists(self):
553        """ Test logging in a user whose profile already exists.
554
555        Login script should only push (and not create) the user profile
556        if a user profile already exists.
557        """
558        os.mkdir('/var/run/shill')
559        os.mkdir(self.new_shill_user_profile_dir)
560        self.touch(self.new_shill_user_profile)
561        self.login()
562        self.assure_method_calls([[ 'InsertUserProfile',
563                                    ('~chronos/shill', self.fake_user_hash) ]],
564                                 'Only InsertUserProfile is called')
565
566
567    def test_login_old_shill_profile(self):
568        """ Test logging in a user with an old-style shill profile.
569
570        Login script should move an old shill user profile into place
571        if a new one does not exist.
572        """
573        os.mkdir('/var/run/shill')
574        self.create_old_shill_user_profile(self.magic_header)
575        self.login()
576        self.assure(not os.path.exists(self.old_shill_user_profile),
577                    'Old shill user profile no longer exists')
578        self.assure(not os.path.exists(self.old_shill_user_profile_dir),
579                    'Old shill user profile directory no longer exists')
580        self.assure_exists(self.new_shill_user_profile,
581                           'New shill profile')
582        self.assure(self.magic_header in
583                    self.file_contents(self.new_shill_user_profile),
584                    'Shill user profile contains our magic header')
585        self.assure_method_calls([[ 'InsertUserProfile',
586                                    ('~chronos/shill', self.fake_user_hash) ]],
587                                 'Only InsertUserProfile is called')
588
589
590    def make_symlink(self, path):
591        """ Create a symbolic link named |path|.
592
593        @param path string pathname of the symbolic link.
594
595        """
596        os.symlink('/etc/hosts', path)
597
598
599    def make_special_file(self, path):
600        """ Create a special file named |path|.
601
602        @param path string pathname of the special file.
603
604        """
605        os.mknod(path, stat.S_IFIFO)
606
607
608    def make_bad_owner(self, path):
609        """ Create a regular file with a strange ownership.
610
611        @param path string pathname of the file.
612
613        """
614        self.touch(path)
615        os.lchown(path, 1000, 1000)
616
617
618    def test_login_invalid_old_shill_profile(self):
619        """ Test logging in with an invalid old-style shill profile.
620
621        Login script should ignore non-regular files or files not owned
622        by the correct user.  The original file should be removed.
623
624        """
625        os.mkdir('/var/run/shill')
626        for file_creation_method in (self.make_symlink,
627                                     self.make_special_file,
628                                     os.mkdir,
629                                     self.make_bad_owner):
630            os.mkdir(self.old_shill_user_profile_dir)
631            file_creation_method(self.old_shill_user_profile)
632            self.login()
633            self.assure(not os.path.exists(self.old_shill_user_profile),
634                        'Old shill user profile no longer exists')
635            self.assure(not os.path.exists(self.old_shill_user_profile_dir),
636                        'Old shill user profile directory no longer exists')
637            self.assure(not os.path.exists(self.new_shill_user_profile),
638                        'New shill profile was not created')
639            self.assure_method_calls([[ 'CreateProfile', '~chronos/shill' ],
640                                      [ 'InsertUserProfile',
641                                        ('~chronos/shill',
642                                         self.fake_user_hash) ]],
643                                     'CreateProfile and InsertUserProfile '
644                                     'are called')
645            os.unlink('/var/run/shill/user_profiles/chronos')
646
647
648    def test_login_ignore_old_shill_profile(self):
649        """ Test logging in with both an old and new profile present.
650
651        Login script should ignore an old shill user profile if a new one
652        exists.
653
654        """
655        os.mkdir('/var/run/shill')
656        self.create_new_shill_user_profile('')
657        self.create_old_shill_user_profile(self.magic_header)
658        self.login()
659        self.assure(os.path.exists(self.old_shill_user_profile),
660                    'Old shill user profile still exists')
661        self.assure_exists(self.new_shill_user_profile,
662                           'New shill profile')
663        self.assure(self.magic_header not in
664                    self.file_contents(self.new_shill_user_profile),
665                    'Shill user profile does not contain our magic header')
666        self.assure_method_calls([[ 'InsertUserProfile',
667                                    ('~chronos/shill', self.fake_user_hash) ]],
668                                 'Only InsertUserProfile is called')
669
670
671    def test_login_flimflam_profile(self):
672        """ Test logging in with an old flimflam profile.
673
674        Login script should move a flimflam user profile into place
675        if a shill one does not exist.
676
677        """
678        os.mkdir('/var/run/shill')
679        self.create_flimflam_user_profile(self.magic_header)
680        self.login()
681        self.assure(not os.path.exists(self.flimflam_user_profile),
682                    'Flimflam user profile no longer exists')
683        self.assure(not os.path.exists(self.flimflam_user_profile_dir),
684                    'Flimflam user profile directory no longer exists')
685        self.assure_exists(self.new_shill_user_profile,
686                           'New shill profile')
687        self.assure(self.magic_header in
688                    self.file_contents(self.new_shill_user_profile),
689                    'Shill user profile contains our magic header')
690        self.assure_method_calls([[ 'InsertUserProfile',
691                                    ('~chronos/shill', self.fake_user_hash) ]],
692                                 'Only InsertUserProfile is called')
693
694
695    def test_login_ignore_flimflam_profile(self):
696        """ Test logging in with both a flimflam profile and a new profile.
697
698        Login script should ignore an old flimflam user profile if a new
699        one exists.
700
701        """
702        os.mkdir('/var/run/shill')
703        self.create_flimflam_user_profile(self.magic_header)
704        self.create_new_shill_user_profile('')
705        self.login()
706        self.assure_exists(self.new_shill_user_profile,
707                           'New shill profile')
708        self.assure(self.magic_header not in
709                    self.file_contents(self.new_shill_user_profile),
710                    'Shill user profile does not contain our magic header')
711        self.assure_method_calls([[ 'InsertUserProfile',
712                                    ('~chronos/shill', self.fake_user_hash) ]],
713                                 'Only InsertUserProfile is called')
714
715
716    def test_login_prefer_old_shill_profile(self):
717        """ Test logging in with both a flimflam and old-style shill profile.
718
719        Login script should use the old shill user profile in preference
720        to a flimflam user profile if the new user profile does not
721        exist.
722
723        """
724        os.mkdir('/var/run/shill')
725        self.create_flimflam_user_profile('')
726        self.create_old_shill_user_profile(self.magic_header)
727        self.login()
728        self.assure(not os.path.exists(self.flimflam_user_profile),
729                    'Flimflam user profile was removed')
730        self.assure(not os.path.exists(self.old_shill_user_profile),
731                    'Old shill user profile no longer exists')
732        self.assure_exists(self.new_shill_user_profile,
733                           'New shill profile')
734        self.assure(self.magic_header in
735                    self.file_contents(self.new_shill_user_profile),
736                    'Shill user profile contains our magic header')
737        self.assure_method_calls([[ 'InsertUserProfile',
738                                    ('~chronos/shill', self.fake_user_hash) ]],
739                                 'Only InsertUserProfile is called')
740
741
742    def test_login_multi_profile(self):
743        """ Test signalling shill about multiple logged-in users.
744
745        Login script should not create multiple profiles in parallel
746        if called more than once without an intervening logout.  Only
747        the initial user profile should be created.
748
749        """
750        os.mkdir('/var/run/shill')
751        self.create_new_shill_user_profile('')
752
753        # First logged-in user should create a profile (tested above).
754        self.login()
755
756        # Clear the mock method-call queue.
757        self.mock_flimflam.get_method_calls()
758
759        for attempt in range(5):
760            self.login()
761            self.assure_method_calls([], 'No more profiles are added to shill')
762            profile_links = os.listdir('/var/run/shill/user_profiles')
763            self.assure(len(profile_links) == 1, 'Only one profile exists')
764            self.assure(profile_links[0] == 'chronos',
765                        'The profile link is for the chronos user')
766            self.assure_is_link_to('/var/run/shill/log',
767                                   self.user_cryptohome_log_dir,
768                                   'Shill log link for chronos')
769
770
771    def test_logout(self):
772        """ Test the logout process. """
773        os.makedirs('/var/run/shill/user_profiles')
774        os.makedirs(self.guest_shill_user_profile_dir)
775        os.makedirs(self.guest_shill_user_log_dir)
776        self.touch('/var/run/state/logged-in')
777        self.logout()
778        self.assure(not os.path.exists('/var/run/shill/user_profiles'),
779                    'User profile directory was removed')
780        self.assure(not os.path.exists(self.guest_shill_user_profile_dir),
781                    'Guest user profile directory was removed')
782        self.assure(not os.path.exists(self.guest_shill_user_log_dir),
783                    'Guest user log directory was removed')
784        self.assure_method_calls([[ 'PopAllUserProfiles', '' ]],
785                                 'PopAllUserProfiles is called')
786