1##
2# Copyright (c) 2023 Apple Inc. All rights reserved.
3#
4# @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5#
6# This file contains Original Code and/or Modifications of Original Code
7# as defined in and that are subject to the Apple Public Source License
8# Version 2.0 (the 'License'). You may not use this file except in
9# compliance with the License. The rights granted to you under the License
10# may not be used to create, or enable the creation or redistribution of,
11# unlawful or unlicensed copies of an Apple operating system, or to
12# circumvent, violate, or enable the circumvention or violation of, any
13# terms of an Apple operating system software license agreement.
14#
15# Please obtain a copy of the License at
16# http://www.opensource.apple.com/apsl/ and read it before using this file.
17#
18# The Original Code and all software distributed under the License are
19# distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20# EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21# INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22# FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23# Please see the License for the specific language governing rights and
24# limitations under the License.
25#
26# @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27##
28
29# pylint: disable=invalid-name
30
31""" Unit test examples
32
33    This is not a real test suite. It only demonstrates various approaches developers
34    can use to write a test.
35"""
36
37import contextlib
38import io
39import unittest.mock
40from lldbtest.testcase import LLDBTestCase
41import lldb
42
43from lldbmock.utils import lookup_type
44
45# Import macro function to be tested.
46from process import ShowTask, P_LHASTASK, TF_HASPROC
47
48
49class TestExamples(LLDBTestCase):
50    """ Unit test examples. """
51
52    ROUNDED_UP_PROC_SIZE = 2048
53
54    # Mock global variable value (accessed by the macro being)
55    @unittest.mock.patch('xnu.kern.globals.proc_struct_size', ROUNDED_UP_PROC_SIZE)
56    def test_function(self):
57        """ This test shows how to run complex function against a mock. """
58
59        self.reset_mocks()
60
61        PROC_ADDR = 0xffffffff90909090
62        TASK_ADDR = PROC_ADDR + self.ROUNDED_UP_PROC_SIZE
63        PROC_RO_ADDR = 0xffffff0040404040
64
65        # Create fake proc_t instance at 0xffffffff90909090
66        proc = self.create_mock('proc', PROC_ADDR).fromDict({
67            'p_pid': 12345,
68            'p_lflag': P_LHASTASK,
69            'p_comm': b'test-proc\0'
70        })
71
72        # Create task which is expected to be placed + sizeof(proc)
73        task = self.create_mock('task', TASK_ADDR).fromDict({
74            'effective_policy': {
75                'tep_sup_active': 0,
76                'tep_darwinbg': 0,
77                'tep_lowpri_cpu': 1
78            },
79            't_flags': TF_HASPROC
80        })
81
82        # Created shared proc_ro reference from both task/proc
83        self.create_mock('proc_ro', PROC_RO_ADDR)
84        proc.p_proc_ro = PROC_RO_ADDR
85        task.bsd_info_ro = PROC_RO_ADDR
86
87        # Capture stdout and check expected output
88        stdout = io.StringIO()
89        with contextlib.redirect_stdout(stdout):
90            ShowTask([f'{TASK_ADDR:#x}'])
91
92        # Note: Not the best way of writing a unit test.
93        expected = (
94            'task                 vm_map               ipc_space            #acts flags'
95            '    pid   process              io_policy  wq_state  command'
96            f'                         \n{TASK_ADDR:#x}   0x0                  0x0    '
97            f'                  0        12345   {PROC_ADDR:#x}           L  0  0  0'
98            '   test-proc                       \n'
99        )
100        self.assertEqual(stdout.getvalue(), expected)
101
102    def test_command(self):
103        """ Test a simple LLDB command from user's CLI.
104
105            Creates mock of a structure and prints out member by using LLDB
106            expression.
107        """
108
109        self.reset_mocks()
110
111        PROC_ADDR = 0xffffffff90909090
112
113        self.create_mock('proc', PROC_ADDR).fromDict({
114            'p_pid': 12345,
115            'p_lflag': P_LHASTASK,
116            'p_comm': b'unit-test-proc\0'
117        })
118
119        res = self.run_command(f'p/x ((proc_t){PROC_ADDR:#x})->p_comm')
120        self.assertEqual(res.GetOutput(), '(command_t) "unit-test-proc"\n')
121        self.assertTrue(res.Succeeded())
122
123    @unittest.skipIf(LLDBTestCase.kernel().startswith('mach.release'),
124                     "Not available in RELEASE embedded")
125    def test_sbapi(self):
126        """ Test SBAPI on top of a mocked target. """
127
128        DOFHELP_ADDR = 0xffffffff11220000
129
130        # Construct simple data structure mock.
131        self.create_mock('struct dof_helper', DOFHELP_ADDR).fromDict({
132            'dofhp_mod': b'mock-mod',
133            'dofhp_addr': 0x1234,
134            'dofhp_dof': 0x5678
135        })
136
137        # Construct SBValue on top of the mock.
138        addr = self.target.ResolveLoadAddress(DOFHELP_ADDR)
139        sbv = self.target.CreateValueFromAddress('test',
140                            addr, lookup_type('dof_helper_t'))
141
142        self.assertTrue(sbv.IsValid() and sbv.error.success)
143
144        # Check that LLDB SBAPI returns correct values from mock.
145        err = lldb.SBError()
146        self.assertEqual(
147            sbv.GetChildMemberWithName('dofhp_mod').GetData().GetString(err, 0),
148            "mock-mod"
149        )
150        self.assertEqual(
151            sbv.GetChildMemberWithName('dofhp_addr').GetValueAsUnsigned(),
152            0x1234
153        )
154        self.assertEqual(
155            sbv.GetChildMemberWithName('dofhp_dof').GetValueAsUnsigned(),
156            0x5678
157        )
158
159    @unittest.skipIf(LLDBTestCase.arch() != 'arm64e', "Only on arm64e")
160    def test_skip_arch(self):
161        """ Example of architecture specific test. """
162
163        self.assertEqual(self.target.triple.split('-', 1)[0], 'arm64e')
164
165    @unittest.skipIf(LLDBTestCase.variant() != 'DEVELOPMENT', "DEVELOPMENT kernel only")
166    def test_skip_development(self):
167        """ Test that runs only on release kernel. """
168
169        self.assertEqual(LLDBTestCase.variant(), "DEVELOPMENT")
170