• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (C) 2020 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Test manifest split."""
15
16import json
17import os
18import re
19import subprocess
20import tempfile
21import unittest
22import unittest.mock
23import xml.etree.ElementTree as ET
24
25from treble.split import manifest_split
26
27
28class ManifestSplitTest(unittest.TestCase):
29
30  def test_read_config(self):
31    with tempfile.NamedTemporaryFile('w+t') as test_config:
32      test_config.write("""
33        <config>
34          <add_project name="add1" />
35          <add_project name="add2" />
36          <remove_project name="remove1" />
37          <remove_project name="remove2" />
38          <path_mapping pattern="p1.*" sub="$0" />
39        </config>""")
40      test_config.flush()
41      config = manifest_split.ManifestSplitConfig.from_config_files(
42          [test_config.name])
43      self.assertEqual(config.remove_projects, {
44          'remove1': test_config.name,
45          'remove2': test_config.name
46      })
47      self.assertEqual(config.add_projects, {
48          'add1': test_config.name,
49          'add2': test_config.name
50      })
51      self.assertEqual(config.path_mappings, [
52          manifest_split.PathMappingConfig(re.compile('p1.*'), '$0'),
53      ])
54
55  def test_get_repo_projects_from_manifest(self):
56    manifest_contents = """
57      <manifest>
58        <project name="platform/project1" path="system/project1" />
59        <project name="platform/project2" path="system/project2" />
60        <project name="platform/project3" path="system/project3" />
61      </manifest>"""
62    manifest = ET.ElementTree(ET.fromstring(manifest_contents))
63    projects = manifest_split.get_repo_projects(
64        None, manifest, path_mappings=[])
65    self.assertDictEqual(
66        {
67            'system/project1': 'platform/project1',
68            'system/project2': 'platform/project2',
69            'system/project3': 'platform/project3',
70        }, projects)
71
72
73  def test_get_repo_projects(self):
74    with tempfile.NamedTemporaryFile('w+t') as repo_list_file:
75      repo_list_file.write("""
76        system/project1 : platform/project1
77        system/project2 : platform/project2""")
78      repo_list_file.flush()
79      repo_projects = manifest_split.get_repo_projects(
80          repo_list_file.name, None, path_mappings=[])
81      self.assertEqual(
82          repo_projects, {
83              'system/project1': 'platform/project1',
84              'system/project2': 'platform/project2',
85          })
86
87  def test_get_repo_projects_with_mappings(self):
88    with tempfile.NamedTemporaryFile('w+t') as repo_list_file:
89      repo_list_file.write("""
90        overlay/system/project1 : platform/project1
91        system/project2 : platform/project2
92        hide/this/one : platform/project3""")
93      repo_list_file.flush()
94      path_mappings = [
95          manifest_split.PathMappingConfig(re.compile('^overlay/(.*)'), '\\1'),
96          manifest_split.PathMappingConfig(re.compile('^hide/this/one.*'), ''),
97      ]
98
99      repo_projects = manifest_split.get_repo_projects(repo_list_file.name,
100                                                       None,
101                                                       path_mappings)
102      self.assertEqual(
103          repo_projects, {
104              'system/project1': 'platform/project1',
105              'system/project2': 'platform/project2',
106          })
107
108  def test_get_module_info(self):
109    with tempfile.NamedTemporaryFile('w+t') as module_info_file:
110      module_info_file.write("""{
111        "target1a": { "class": ["EXECUTABLES"], "path": ["system/project1"], "dependencies": ["target2"] },
112        "target1b": { "class": ["EXECUTABLES"], "path": ["system/project1"], "dependencies": ["target3", "target42"] },
113        "target2": { "class": ["SHARED_LIBRARIES"], "path": ["out/project2"], "dependencies": [] },
114        "target3": { "class": ["SHARED_LIBRARIES"], "path": ["vendor/google/project3"], "dependencies": ["x", "y", "z"] },
115        "target4a": { "class": ["APPS"], "path": ["system/project4"], "dependencies": ["out/target/common/obj/JAVA_LIBRARIES/target4b_intermediates/classes-header.jar"] },
116        "target4b": { "class": ["JAVA_LIBRARIES"],  "path": ["system/project4"], "dependencies": [] }
117      }""")
118      module_info_file.flush()
119      repo_projects = {
120          'system/project1': 'platform/project1',
121          'system/project4': 'platform/project4',
122          'vendor/google/project3': 'vendor/project3',
123      }
124      ignore_paths = set(['out/'])
125      module_info = manifest_split.ModuleInfo(module_info_file.name,
126                                              repo_projects, ignore_paths)
127      self.assertEqual(
128          module_info.project_modules, {
129              'platform/project1': set(['target1a', 'target1b']),
130              'platform/project4': set(['target4a', 'target4b']),
131              'vendor/project3': set(['target3']),
132          })
133      self.assertEqual(
134          module_info.module_project, {
135              'target1a': 'platform/project1',
136              'target1b': 'platform/project1',
137              'target3': 'vendor/project3',
138              'target4a': 'platform/project4',
139              'target4b': 'platform/project4',
140          })
141      self.assertEqual(
142          module_info.module_class, {
143              'target1a': 'EXECUTABLES',
144              'target1b': 'EXECUTABLES',
145              'target2': 'SHARED_LIBRARIES',
146              'target3': 'SHARED_LIBRARIES',
147              'target4a': 'APPS',
148              'target4b': 'JAVA_LIBRARIES',
149          })
150      self.assertEqual(
151          module_info.module_deps, {
152              'target1a': ['target2'],
153              'target1b': ['target3', 'target42'],
154              'target2': [],
155              'target3': ['x', 'y', 'z'],
156              'target4a': ['target4b'],
157              'target4b': [],
158          })
159
160  def test_get_module_info_raises_on_unknown_module_path(self):
161    with tempfile.NamedTemporaryFile('w+t') as module_info_file:
162      module_info_file.write("""{
163        "target1": { "class": ["EXECUTABLES"], "path": ["system/unknown/project1"], "dependencies": [] }
164      }""")
165      module_info_file.flush()
166      repo_projects = {}
167      ignore_paths = set()
168      with self.assertRaisesRegex(ValueError,
169                                  'Unknown module path for module target1'):
170        manifest_split.ModuleInfo(module_info_file.name, repo_projects,
171                                  ignore_paths)
172
173  @unittest.mock.patch.object(subprocess, 'check_output', autospec=True)
174  def test_get_ninja_inputs(self, mock_check_output):
175    mock_check_output.return_value = b"""
176    path/to/input1
177    path/to/input2
178    path/to/TEST_MAPPING
179    path/to/MODULE_LICENSE_GPL
180    """
181
182    inputs = manifest_split.get_ninja_inputs('unused', 'unused', ['droid'])
183    self.assertEqual(inputs, {'path/to/input1', 'path/to/input2'})
184
185  @unittest.mock.patch.object(subprocess, 'check_output', autospec=True)
186  def test_get_ninja_inputs_includes_test_mapping(self, mock_check_output):
187    mock_check_output.return_value = b"""
188    path/to/input1
189    path/to/input2
190    path/to/TEST_MAPPING
191    """
192
193    inputs = manifest_split.get_ninja_inputs('unused', 'unused',
194                                             ['droid', 'test_mapping'])
195    self.assertEqual(
196        inputs, {'path/to/input1', 'path/to/input2', 'path/to/TEST_MAPPING'})
197
198  @unittest.mock.patch.object(subprocess, 'check_output', autospec=True)
199  def test_get_kati_makefiles(self, mock_check_output):
200    with tempfile.TemporaryDirectory() as temp_dir:
201      os.chdir(temp_dir)
202
203      makefiles = [
204          'device/oem1/product1.mk',
205          'device/oem2/product2.mk',
206          'device/google/google_product.mk',
207          'overlays/oem_overlay/device/oem3/product3.mk',
208          'packages/apps/Camera/Android.mk',
209      ]
210      for makefile in makefiles:
211        os.makedirs(os.path.dirname(makefile))
212        os.mknod(makefile)
213
214      symlink_src = os.path.join(temp_dir, 'vendor/oem4/symlink_src.mk')
215      os.makedirs(os.path.dirname(symlink_src))
216      os.mknod(symlink_src)
217      symlink_dest = 'device/oem4/symlink_dest.mk'
218      os.makedirs(os.path.dirname(symlink_dest))
219      os.symlink(symlink_src, symlink_dest)
220      # Only append the symlink destination, not where the symlink points to.
221      # (The Kati stamp file does not resolve symlink sources.)
222      makefiles.append(symlink_dest)
223
224      # Mock the output of ckati --dump_stamp_tool:
225      mock_check_output.return_value = '\n'.join(makefiles).encode()
226
227      kati_makefiles = manifest_split.get_kati_makefiles(
228          'stamp-file', ['overlays/oem_overlay/'])
229      self.assertEqual(
230          kati_makefiles,
231          set([
232              # Regular product makefiles
233              'device/oem1/product1.mk',
234              'device/oem2/product2.mk',
235              # Product makefile remapped from an overlay
236              'device/oem3/product3.mk',
237              # Product makefile symlink and its source
238              'device/oem4/symlink_dest.mk',
239              'vendor/oem4/symlink_src.mk',
240          ]))
241
242  def test_scan_repo_projects(self):
243    repo_projects = {
244        'system/project1': 'platform/project1',
245        'system/project2': 'platform/project2',
246    }
247    self.assertEqual(
248        manifest_split.scan_repo_projects(repo_projects,
249                                          'system/project1/path/to/file.h'),
250        'system/project1')
251    self.assertEqual(
252        manifest_split.scan_repo_projects(
253            repo_projects, 'system/project2/path/to/another_file.cc'),
254        'system/project2')
255    self.assertIsNone(
256        manifest_split.scan_repo_projects(
257            repo_projects, 'system/project3/path/to/unknown_file.h'))
258
259  def test_get_input_projects(self):
260    repo_projects = {
261        'system/project1': 'platform/project1',
262        'system/project2': 'platform/project2',
263        'system/project4': 'platform/project4',
264    }
265    inputs = [
266        'system/project1/path/to/file.h',
267        'out/path/to/out/file.h',
268        'system/project2/path/to/another_file.cc',
269        'system/project3/path/to/unknown_file.h',
270        '/tmp/absolute/path/file.java',
271    ]
272    self.assertEqual(
273        manifest_split.get_input_projects(repo_projects, inputs), {
274            'platform/project1': ['system/project1/path/to/file.h'],
275            'platform/project2': ['system/project2/path/to/another_file.cc'],
276        })
277
278  def test_update_manifest(self):
279    manifest_contents = """
280      <manifest>
281        <project name="platform/project1" path="system/project1" />
282        <project name="platform/project2" path="system/project2" />
283        <project name="platform/project3" path="system/project3" />
284      </manifest>"""
285    input_projects = set(['platform/project1', 'platform/project3'])
286    remove_projects = set(['platform/project3'])
287    manifest = manifest_split.update_manifest(
288        ET.ElementTree(ET.fromstring(manifest_contents)), input_projects,
289        remove_projects)
290
291    projects = manifest.getroot().findall('project')
292    self.assertEqual(len(projects), 1)
293    self.assertEqual(
294        ET.tostring(projects[0]).strip().decode(),
295        '<project name="platform/project1" path="system/project1" />')
296
297  @unittest.mock.patch.object(subprocess, 'check_output', autospec=True)
298  def test_create_split_manifest(self, mock_check_output):
299    with tempfile.NamedTemporaryFile('w+t') as repo_list_file, \
300      tempfile.NamedTemporaryFile('w+t') as manifest_file, \
301      tempfile.NamedTemporaryFile('w+t') as module_info_file, \
302      tempfile.NamedTemporaryFile('w+t') as config_file, \
303      tempfile.NamedTemporaryFile('w+t') as split_manifest_file, \
304      tempfile.TemporaryDirectory() as temp_dir:
305
306      os.chdir(temp_dir)
307
308      repo_list_file.write("""
309        system/project1 : platform/project1
310        system/project2 : platform/project2
311        system/project3 : platform/project3
312        system/project4 : platform/project4
313        system/project5 : platform/project5
314        system/project6 : platform/project6
315        system/project7 : platform/project7
316        system/project8 : platform/project8
317        system/project9 : platform/project9
318        vendor/project1 : vendor/project1""")
319      repo_list_file.flush()
320
321      manifest_file.write("""
322        <manifest>
323          <project name="platform/project1" path="system/project1" />
324          <project name="platform/project2" path="system/project2" />
325          <project name="platform/project3" path="system/project3" />
326          <project name="platform/project4" path="system/project4" />
327          <project name="platform/project5" path="system/project5" />
328          <project name="platform/project6" path="system/project6" />
329          <project name="platform/project7" path="system/project7" />
330          <project name="platform/project8" path="system/project8" />
331          <project name="platform/project9" path="system/project9" />
332          <project name="vendor/project1" path="vendor/project1" />
333        </manifest>""")
334      manifest_file.flush()
335
336      module_info_file.write("""{
337        "droid": { "class": ["EXECUTABLES"], "path": ["system/project1"], "dependencies": [] },
338        "target_a": { "class": ["EXECUTABLES"], "path": ["out/project2"], "dependencies": ["unknown_module_a"] },
339        "target_b": { "class": ["EXECUTABLES"], "path": ["system/project3"], "dependencies": ["target_f", "unknown_module_b"] },
340        "target_c": { "class": ["EXECUTABLES"], "path": ["system/project4"], "dependencies": [] },
341        "target_d": { "class": ["EXECUTABLES"], "path": ["system/project5"], "dependencies": [] },
342        "target_e": { "class": ["EXECUTABLES"], "path": ["system/project6"], "dependencies": [] },
343        "target_f": { "class": ["HEADER_LIBRARIES"], "path": ["system/project7"], "dependencies": [] },
344        "target_g": { "class": ["SHARED_LIBRARIES"], "path": ["system/project8"], "dependencies": ["target_h"] },
345        "target_h": { "class": ["HEADER_LIBRARIES"], "path": ["system/project9"], "dependencies": [] }
346      }""")
347      module_info_file.flush()
348
349      # droid needs inputs from project1 and project3
350      ninja_inputs_droid = b"""
351      system/project1/file1
352      system/project1/file2
353      system/project3/file1
354      """
355
356      # target_b (indirectly included due to being in project3) needs inputs
357      # from project3 and project4
358      ninja_inputs_target_b = b"""
359      system/project3/file2
360      system/project4/file1
361      """
362
363      # target_c (indirectly included due to being in project4) needs inputs
364      # from only project4
365      ninja_inputs_target_c = b"""
366      system/project4/file2
367      system/project4/file3
368      """
369
370      product_makefile = 'vendor/project1/product.mk'
371      os.makedirs(os.path.dirname(product_makefile))
372      os.mknod(product_makefile)
373      kati_stamp_dump = product_makefile.encode()
374
375      mock_check_output.side_effect = [
376          ninja_inputs_droid,
377          kati_stamp_dump,
378          ninja_inputs_target_b,
379          ninja_inputs_target_c,
380      ]
381
382      # The config file says to manually include project6
383      config_file.write("""
384        <config>
385          <add_project name="platform/project6" />
386        </config>""")
387      config_file.flush()
388
389      debug_file = os.path.join(temp_dir, 'debug.json')
390
391      manifest_split.create_split_manifest(
392          ['droid'], manifest_file.name, split_manifest_file.name,
393          [config_file.name], repo_list_file.name, 'build-target.ninja',
394          'ninja', module_info_file.name, 'unused kati stamp',
395          ['unused overlay'], [], debug_file)
396      split_manifest = ET.parse(split_manifest_file.name)
397      split_manifest_projects = [
398          child.attrib['name']
399          for child in split_manifest.getroot().findall('project')
400      ]
401      self.assertEqual(
402          split_manifest_projects,
403          [
404              # From droid
405              'platform/project1',
406              # From droid
407              'platform/project3',
408              # From target_b (module within project3, indirect dependency)
409              'platform/project4',
410              # Manual inclusion from config file
411              'platform/project6',
412              # From target_b (depends on target_f header library)
413              'platform/project7',
414              # Inclusion from the Kati makefile stamp
415              'vendor/project1',
416          ])
417
418      with open(debug_file) as debug_fp:
419        debug_data = json.load(debug_fp)
420
421        # Dependency for droid, but no other adjacent modules
422        self.assertTrue(debug_data['platform/project1']['direct_input'])
423        self.assertFalse(debug_data['platform/project1']['adjacent_input'])
424        self.assertFalse(debug_data['platform/project1']['deps_input'])
425
426        # Dependency for droid and an adjacent module
427        self.assertTrue(debug_data['platform/project3']['direct_input'])
428        self.assertTrue(debug_data['platform/project3']['adjacent_input'])
429        self.assertFalse(debug_data['platform/project3']['deps_input'])
430
431        # Dependency only for an adjacent module
432        self.assertFalse(debug_data['platform/project4']['direct_input'])
433        self.assertTrue(debug_data['platform/project4']['adjacent_input'])
434        self.assertFalse(debug_data['platform/project4']['deps_input'])
435
436        # Included via header library
437        self.assertFalse(debug_data['platform/project7']['direct_input'])
438        self.assertFalse(debug_data['platform/project7']['adjacent_input'])
439        self.assertTrue(debug_data['platform/project7']['deps_input'])
440
441        # Included due to the config file
442        self.assertEqual(
443            debug_data['platform/project6']['manual_add_config'],
444            config_file.name)
445
446        # Included due to the Kati makefile stamp
447        self.assertEqual(debug_data['vendor/project1']['kati_makefiles'][0],
448                         product_makefile)
449
450  @unittest.mock.patch.object(manifest_split, 'get_ninja_inputs', autospec=True)
451  @unittest.mock.patch.object(manifest_split, 'get_kati_makefiles', autospec=True)
452  @unittest.mock.patch.object(manifest_split.ModuleInfo, '__init__', autospec=True)
453  def test_create_split_manifest_skip_kati_module_info(self, mock_init,
454                                                       mock_get_kati_makefiles,
455                                                       mock_get_ninja_inputs):
456    with tempfile.NamedTemporaryFile('w+t') as repo_list_file, \
457            tempfile.NamedTemporaryFile('w+t') as manifest_file, \
458            tempfile.NamedTemporaryFile('w+t') as module_info_file, \
459            tempfile.NamedTemporaryFile('w+t') as config_file, \
460            tempfile.NamedTemporaryFile('w+t') as split_manifest_file, \
461            tempfile.TemporaryDirectory() as temp_dir:
462
463      os.chdir(temp_dir)
464
465      manifest_file.write("""
466        <manifest>
467        </manifest>""")
468      manifest_file.flush()
469
470      manifest_split.create_split_manifest(
471          targets=['droid'],
472          manifest_file=manifest_file.name,
473          split_manifest_file=split_manifest_file.name,
474          config_files=[],
475          repo_list_file=repo_list_file.name,
476          ninja_build_file='build-target.ninja',
477          ninja_binary='ninja',
478          kati_stamp_file=None,
479          module_info_file=None,
480          overlays=[],
481          installed_prebuilts=[],
482          debug_file=None)
483
484    mock_get_ninja_inputs.assert_called_with(
485        'ninja', 'build-target.ninja', ['droid'])
486    mock_get_kati_makefiles.assert_not_called()
487    mock_init.assert_not_called()
488
489  @unittest.mock.patch.object(subprocess, 'check_output', autospec=True)
490  def test_create_split_manifest_installed_prebuilt(self, mock_check_output):
491
492    # The purpose of this test is to verify that create_split_manifests treats
493    # installed prebuilts as projects, even though the installed prebuilts are
494    # not in the manifest. This use case occurs when installed prebuilts
495    # contribute modules to the build, but the installed prebuilts themselves
496    # aren't sourced from the manifest.
497
498    with tempfile.NamedTemporaryFile('w+t') as repo_list_file, \
499      tempfile.NamedTemporaryFile('w+t') as manifest_file, \
500      tempfile.NamedTemporaryFile('w+t') as module_info_file, \
501      tempfile.NamedTemporaryFile('w+t') as split_manifest_file, \
502      tempfile.TemporaryDirectory() as temp_dir:
503
504      os.chdir(temp_dir)
505
506      repo_list_file.write("""
507        system/project1 : platform/project1
508        vendor/project1 : vendor/project1""")
509      repo_list_file.flush()
510
511      # Here we have small manifest that does not include "prebuilt/project3"
512      # or "prebuilt/project4".
513
514      manifest_file.write("""
515        <manifest>
516          <project name="platform/project1" path="system/project1" />
517          <project name="vendor/project1" path="vendor/project1" />
518        </manifest>""")
519      manifest_file.flush()
520
521      # Here's the module_info.json file. It contains modules whose paths are
522      # "prebuilt/project3" and "prebult/project4", which are not found in the
523      # manifest. Normally create_split_manifest doesn't tolerate a path that
524      # doesn't correspond to a manifest project. However, this test verifies
525      # that you can use these modules if you tell create_split_manifest about
526      # the installed prebuilts via a parameter.
527
528      module_info_file.write("""{
529        "droid": { "class": ["EXECUTABLES"], "path": ["system/project1"], "dependencies": [] },
530        "target_a": { "class": ["EXECUTABLES"], "path": ["system/project1"], "dependencies": ["target_b", "target_c"] },
531        "target_b": { "class": ["SHARED_LIBRARIES"], "path": ["prebuilt/project3"], "dependencies": [] },
532        "target_c": { "class": ["SHARED_LIBRARIES"], "path": ["prebuilt/project4"], "dependencies": [] }
533      }""")
534      module_info_file.flush()
535
536      # droid needs inputs from project1
537      ninja_inputs_droid = b"""
538      system/project1/file1
539      """
540
541      # target_a needs inputs from prebuilt/project3 and prebuilt/project4
542      ninja_inputs_target_a = b"""
543      prebuilt/project3/file2
544      prebuilt/project4/file3
545      """
546
547      # target_b needs inputs from prebuilt/project3
548      ninja_inputs_target_b = b"""
549      prebuilt/project3/file4
550      """
551
552      # target_c needs inputs from prebuilt/project4
553      ninja_inputs_target_c = b"""
554      prebuilt/project4/file5
555      """
556
557      product_makefile = 'vendor/project1/product.mk'
558      os.makedirs(os.path.dirname(product_makefile))
559      os.mknod(product_makefile)
560      kati_stamp_dump = product_makefile.encode()
561
562      mock_check_output.side_effect = [
563          ninja_inputs_droid,
564          kati_stamp_dump,
565          ninja_inputs_target_a,
566          ninja_inputs_target_b,
567          ninja_inputs_target_c,
568      ]
569
570      debug_file = os.path.join(temp_dir, 'debug.json')
571
572      manifest_split.create_split_manifest(
573          targets=['droid'],
574          manifest_file=manifest_file.name,
575          split_manifest_file=split_manifest_file.name,
576          config_files=[],
577          repo_list_file=repo_list_file.name,
578          ninja_build_file='build-target.ninja',
579          ninja_binary='ninja',
580          module_info_file=module_info_file.name,
581          kati_stamp_file='unused kati stamp',
582          overlays=['unused overlay'],
583
584          # This is a key part of the test. Passing these two "projects" as
585          # prebuilts allows create_split_manifest to recognize them as
586          # projects even though they are not in the manifest.
587
588          installed_prebuilts=['prebuilt/project3', 'prebuilt/project4'],
589
590          debug_file = debug_file)
591
592      split_manifest = ET.parse(split_manifest_file.name)
593
594      split_manifest_projects = [
595          child.attrib['name']
596          for child in split_manifest.getroot().findall('project')
597      ]
598
599      # Note that the installed prebuilts do not appear in the final split
600      # manfiest output because they were not in the manifest to begin with.
601
602      self.assertEqual(
603          split_manifest_projects,
604          [
605              # From droid
606              'platform/project1',
607              # Inclusion from the Kati makefile stamp
608              'vendor/project1',
609          ])
610
611      with open(debug_file) as debug_fp:
612        debug_data = json.load(debug_fp)
613
614        # Dependency for droid, but no other adjacent modules
615        self.assertTrue(debug_data['platform/project1']['direct_input'])
616        self.assertFalse(debug_data['platform/project1']['adjacent_input'])
617        self.assertFalse(debug_data['platform/project1']['deps_input'])
618
619        # Included due to the Kati makefile stamp
620        self.assertEqual(debug_data['vendor/project1']['kati_makefiles'][0],
621                         product_makefile)
622
623
624if __name__ == '__main__':
625  unittest.main()
626