1#!/usr/bin/env python
2# Copyright 2015 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""
7Unit tests for the contents of shared_prefs.py (mostly SharedPrefs).
8"""
9
10import logging
11import unittest
12
13from devil import devil_env
14from devil.android import device_utils
15from devil.android.sdk import shared_prefs
16from devil.android.sdk import version_codes
17
18with devil_env.SysPath(devil_env.PYMOCK_PATH):
19  import mock  # pylint: disable=import-error
20
21
22def MockDeviceWithFiles(files=None):
23  if files is None:
24    files = {}
25
26  def file_exists(path):
27    return path in files
28
29  def write_file(path, contents, **_kwargs):
30    files[path] = contents
31
32  def read_file(path, **_kwargs):
33    return files[path]
34
35  device = mock.MagicMock(spec=device_utils.DeviceUtils)
36  device.FileExists = mock.Mock(side_effect=file_exists)
37  device.WriteFile = mock.Mock(side_effect=write_file)
38  device.ReadFile = mock.Mock(side_effect=read_file)
39  return device
40
41
42class SharedPrefsTest(unittest.TestCase):
43
44  def setUp(self):
45    self.device = MockDeviceWithFiles({
46      '/data/data/com.some.package/shared_prefs/prefs.xml':
47          "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
48          '<map>\n'
49          '  <int name="databaseVersion" value="107" />\n'
50          '  <boolean name="featureEnabled" value="false" />\n'
51          '  <string name="someHashValue">249b3e5af13d4db2</string>\n'
52          '</map>'})
53    self.expected_data = {'databaseVersion': 107,
54                          'featureEnabled': False,
55                          'someHashValue': '249b3e5af13d4db2'}
56
57  def testPropertyLifetime(self):
58    prefs = shared_prefs.SharedPrefs(
59        self.device, 'com.some.package', 'prefs.xml')
60    self.assertEquals(len(prefs), 0)  # collection is empty before loading
61    prefs.SetInt('myValue', 444)
62    self.assertEquals(len(prefs), 1)
63    self.assertEquals(prefs.GetInt('myValue'), 444)
64    self.assertTrue(prefs.HasProperty('myValue'))
65    prefs.Remove('myValue')
66    self.assertEquals(len(prefs), 0)
67    self.assertFalse(prefs.HasProperty('myValue'))
68    with self.assertRaises(KeyError):
69      prefs.GetInt('myValue')
70
71  def testPropertyType(self):
72    prefs = shared_prefs.SharedPrefs(
73        self.device, 'com.some.package', 'prefs.xml')
74    prefs.SetInt('myValue', 444)
75    self.assertEquals(prefs.PropertyType('myValue'), 'int')
76    with self.assertRaises(TypeError):
77      prefs.GetString('myValue')
78    with self.assertRaises(TypeError):
79      prefs.SetString('myValue', 'hello')
80
81  def testLoad(self):
82    prefs = shared_prefs.SharedPrefs(
83        self.device, 'com.some.package', 'prefs.xml')
84    self.assertEquals(len(prefs), 0)  # collection is empty before loading
85    prefs.Load()
86    self.assertEquals(len(prefs), len(self.expected_data))
87    self.assertEquals(prefs.AsDict(), self.expected_data)
88    self.assertFalse(prefs.changed)
89
90  def testClear(self):
91    prefs = shared_prefs.SharedPrefs(
92        self.device, 'com.some.package', 'prefs.xml')
93    prefs.Load()
94    self.assertEquals(prefs.AsDict(), self.expected_data)
95    self.assertFalse(prefs.changed)
96    prefs.Clear()
97    self.assertEquals(len(prefs), 0)  # collection is empty now
98    self.assertTrue(prefs.changed)
99
100  def testCommit(self):
101    type(self.device).build_version_sdk = mock.PropertyMock(
102        return_value=version_codes.LOLLIPOP_MR1)
103    prefs = shared_prefs.SharedPrefs(
104        self.device, 'com.some.package', 'other_prefs.xml')
105    self.assertFalse(self.device.FileExists(prefs.path))  # file does not exist
106    prefs.Load()
107    self.assertEquals(len(prefs), 0)  # file did not exist, collection is empty
108    prefs.SetInt('magicNumber', 42)
109    prefs.SetFloat('myMetric', 3.14)
110    prefs.SetLong('bigNumner', 6000000000)
111    prefs.SetStringSet('apps', ['gmail', 'chrome', 'music'])
112    self.assertFalse(self.device.FileExists(prefs.path))  # still does not exist
113    self.assertTrue(prefs.changed)
114    prefs.Commit()
115    self.assertTrue(self.device.FileExists(prefs.path))  # should exist now
116    self.device.KillAll.assert_called_once_with(prefs.package, exact=True,
117                                                as_root=True, quiet=True)
118    self.assertFalse(prefs.changed)
119
120    prefs = shared_prefs.SharedPrefs(
121        self.device, 'com.some.package', 'other_prefs.xml')
122    self.assertEquals(len(prefs), 0)  # collection is empty before loading
123    prefs.Load()
124    self.assertEquals(prefs.AsDict(), {
125        'magicNumber': 42,
126        'myMetric': 3.14,
127        'bigNumner': 6000000000,
128        'apps': ['gmail', 'chrome', 'music']})  # data survived roundtrip
129
130  def testAsContextManager_onlyReads(self):
131    with shared_prefs.SharedPrefs(
132        self.device, 'com.some.package', 'prefs.xml') as prefs:
133      self.assertEquals(prefs.AsDict(), self.expected_data)  # loaded and ready
134    self.assertEquals(self.device.WriteFile.call_args_list, [])  # did not write
135
136  def testAsContextManager_readAndWrite(self):
137    type(self.device).build_version_sdk = mock.PropertyMock(
138        return_value=version_codes.LOLLIPOP_MR1)
139    with shared_prefs.SharedPrefs(
140        self.device, 'com.some.package', 'prefs.xml') as prefs:
141      prefs.SetBoolean('featureEnabled', True)
142      prefs.Remove('someHashValue')
143      prefs.SetString('newString', 'hello')
144
145    self.assertTrue(self.device.WriteFile.called)  # did write
146    with shared_prefs.SharedPrefs(
147        self.device, 'com.some.package', 'prefs.xml') as prefs:
148      # changes persisted
149      self.assertTrue(prefs.GetBoolean('featureEnabled'))
150      self.assertFalse(prefs.HasProperty('someHashValue'))
151      self.assertEquals(prefs.GetString('newString'), 'hello')
152      self.assertTrue(prefs.HasProperty('databaseVersion'))  # still there
153
154  def testAsContextManager_commitAborted(self):
155    with self.assertRaises(TypeError):
156      with shared_prefs.SharedPrefs(
157          self.device, 'com.some.package', 'prefs.xml') as prefs:
158        prefs.SetBoolean('featureEnabled', True)
159        prefs.Remove('someHashValue')
160        prefs.SetString('newString', 'hello')
161        prefs.SetInt('newString', 123)  # oops!
162
163    self.assertEquals(self.device.WriteFile.call_args_list, [])  # did not write
164    with shared_prefs.SharedPrefs(
165        self.device, 'com.some.package', 'prefs.xml') as prefs:
166      # contents were not modified
167      self.assertEquals(prefs.AsDict(), self.expected_data)
168
169if __name__ == '__main__':
170  logging.getLogger().setLevel(logging.DEBUG)
171  unittest.main(verbosity=2)
172