asset_database_utils.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. """
  2. All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
  3. its licensors.
  4. For complete copyright and license terms please see the LICENSE at the root of this
  5. distribution (the "License"). All use of this software is governed by the License,
  6. or, if provided, by the license below or the license accompanying this file. Do not
  7. remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. """
  10. import binascii
  11. from dataclasses import dataclass, field
  12. import logging
  13. import sqlite3
  14. import os
  15. from typing import List
  16. import ly_test_tools.lumberyard.pipeline_utils as pipeline_utils
  17. # Index for ProductID in Products table in DB
  18. PRODUCT_ID_INDEX = 0
  19. logger = logging.getLogger(__name__)
  20. def do_select(asset_db_path, cmd):
  21. try:
  22. connection = sqlite3.connect(asset_db_path)
  23. # Get ProductID from database
  24. db_rows = connection.execute(cmd)
  25. return_result = db_rows.fetchall()
  26. connection.close()
  27. return return_result
  28. except sqlite3.Error as sqlite_error:
  29. print(f'select on db {asset_db_path} failed with exception {sqlite_error}')
  30. return []
  31. def get_active_platforms_from_db(asset_db_path) -> List[str]:
  32. """Returns a list of platforms that are active in the database, based on what jobs were run"""
  33. platform_rows = do_select(asset_db_path, f"select distinct Platform from Jobs")
  34. # Condense this into a single list of platforms.
  35. platforms = [platform[0] for platform in platform_rows]
  36. return platforms
  37. # Convert a source product path into a db product path
  38. # cache_platform/projectname/product_path
  39. def get_db_product_path(workspace, source_path, cache_platform):
  40. product_path = os.path.join(cache_platform, workspace.project, source_path)
  41. product_path = product_path.replace('\\', '/')
  42. return product_path
  43. def get_product_id(asset_db_path, product_name) -> str:
  44. # Get ProductID from database
  45. product_id = list(do_select(asset_db_path, f"SELECT ProductID FROM Products where ProductName='{product_name}'"))
  46. if len(product_id) == 0:
  47. return product_id # return empty list
  48. return product_id[0][PRODUCT_ID_INDEX] # Get product id from 'first' row
  49. # Retrieve a product_id given a source_path assuming the source is copied into the cache with the same
  50. # name or a product name without cache_platform or projectname prepended
  51. def get_product_id_from_relative(workspace, source_path, asset_platform):
  52. return get_product_id(workspace.paths.asset_db(), get_db_product_path(workspace, source_path, asset_platform))
  53. def get_missing_dependencies(asset_db_path, product_id) -> List[str]:
  54. return list(do_select(asset_db_path, f"SELECT * FROM MissingProductDependencies where ProductPK={product_id}"))
  55. def do_single_transaction(asset_db_path, cmd):
  56. try:
  57. connection = sqlite3.connect(asset_db_path)
  58. cursor = connection.cursor() # SQL cursor used for issuing commands
  59. cursor.execute(cmd)
  60. connection.commit() # Save changes
  61. connection.close()
  62. except sqlite3.Error as sqlite_error:
  63. print(f'transaction on db {asset_db_path} cmd {cmd} failed with exception {sqlite_error}')
  64. def clear_missing_dependencies(asset_db_path, product_id) -> None:
  65. do_single_transaction(asset_db_path, f"DELETE FROM MissingProductDependencies where ProductPK={product_id}")
  66. def clear_all_missing_dependencies(asset_db_path) -> None:
  67. do_single_transaction(asset_db_path, "DELETE FROM MissingProductDependencies;")
  68. @dataclass
  69. class DBProduct:
  70. product_name: str = None
  71. sub_id: int = 0
  72. asset_type: str = None
  73. @dataclass
  74. class DBJob:
  75. job_key: str = None
  76. builder_guid: str = None
  77. status: int = 0
  78. error_count: int = 0
  79. warning_count: int = 0
  80. platform: str = None
  81. # Key: Product ID
  82. products: List[DBProduct] = field(default_factory=list)
  83. @dataclass
  84. class DBSourceAsset:
  85. source_file_name: str = None
  86. uuid: str = None
  87. scan_folder_key: str = field(compare=False, default=None)
  88. id: str = field(compare=False, default=None)
  89. # Key: Job ID
  90. jobs: List[DBJob] = field(default_factory=list)
  91. def compare_expected_asset_to_actual_asset(asset_db_path, expected_asset: DBSourceAsset, asset_path, cache_root):
  92. actual_asset = get_db_source_job_product_info(asset_db_path, asset_path, cache_root)
  93. assert expected_asset.uuid == actual_asset.uuid, \
  94. f"UUID for asset {expected_asset.source_file_name} is '{actual_asset.uuid}', but expected '{expected_asset.uuid}'"
  95. for expected_job in expected_asset.jobs:
  96. for actual_job in actual_asset.jobs:
  97. found_job = False
  98. if expected_job.job_key == actual_job.job_key and expected_job.platform == actual_job.platform:
  99. found_job = True
  100. assert expected_job == actual_job, \
  101. f"Expected job did not match actual job for key {expected_job.job_key} and platform {expected_job.platform}.\nExpected: {expected_job}\nActual: {actual_job}"
  102. # Remove the found job to speed up other searches.
  103. actual_asset.jobs.remove(actual_job)
  104. break
  105. assert found_job, f"For asset {expected_asset.source_file_name}, could not find job with key '{expected_job.job_key}' and platform '{expected_job.platform}'"
  106. # Don't assert on any actual jobs remaining, they could be for platforms not checked by this test, or new job keys not yet handled.
  107. def get_db_source_job_product_info(asset_db_path, filename, cache_root):
  108. source_db = get_source_info_from_filename(asset_db_path, filename)
  109. source = DBSourceAsset()
  110. source.source_file_name = filename
  111. source.id = source_db[0]
  112. source.scan_folder_key = source_db[1]
  113. source.uuid = binascii.hexlify(source_db[2])
  114. jobs_db = get_jobs_for_source(asset_db_path, source.id)
  115. for job_db in jobs_db:
  116. job_id = job_db[0]
  117. job = DBJob()
  118. job.job_key = job_db[1]
  119. job.platform = job_db[3]
  120. job.builder_guid = binascii.hexlify(job_db[4])
  121. job.status = job_db[5]
  122. job.error_count = job_db[6]
  123. job.warning_count = job_db[7]
  124. products_db = get_products_for_job(asset_db_path, job_id)
  125. for product_db in products_db:
  126. product = DBProduct()
  127. product.product_name = product_db[1]
  128. product.sub_id = product_db[2]
  129. product.asset_type = binascii.hexlify(product_db[3])
  130. job.products.append(product)
  131. source.jobs.append(job)
  132. return source
  133. def get_source_info_from_filename(asset_db_path, filename):
  134. sources = do_select(asset_db_path,
  135. f"SELECT SourceID,ScanFolderPK,SourceGuid FROM Sources where SourceName=\"{filename}\"")
  136. assert len(sources) == 1, f"Zero or multiple source assets found when only one was expected for '{filename}'"
  137. return sources[0]
  138. def get_jobs_for_source(asset_db_path, source_id):
  139. return do_select(asset_db_path,
  140. f"SELECT JobID,JobKey,Fingerprint,Platform,BuilderGuid,Status,ErrorCount,WarningCount FROM Jobs where SourcePK={source_id} order by JobKey")
  141. def get_products_for_job(asset_db_path, job_id):
  142. return do_select(asset_db_path,
  143. f"SELECT ProductID,ProductName,SubID,AssetType FROM Products where JobPK={job_id} order by ProductName")