diff --git a/web/api/js/codechecker-api-node/dist/codechecker-api-6.51.0.tgz b/web/api/js/codechecker-api-node/dist/codechecker-api-6.51.0.tgz deleted file mode 100644 index 85d1b1a3ff..0000000000 Binary files a/web/api/js/codechecker-api-node/dist/codechecker-api-6.51.0.tgz and /dev/null differ diff --git a/web/api/js/codechecker-api-node/dist/codechecker-api-6.52.0.tgz b/web/api/js/codechecker-api-node/dist/codechecker-api-6.52.0.tgz new file mode 100644 index 0000000000..6f2f0f6e8c Binary files /dev/null and b/web/api/js/codechecker-api-node/dist/codechecker-api-6.52.0.tgz differ diff --git a/web/api/js/codechecker-api-node/package.json b/web/api/js/codechecker-api-node/package.json index 145d212fa2..2ddf5aac4a 100644 --- a/web/api/js/codechecker-api-node/package.json +++ b/web/api/js/codechecker-api-node/package.json @@ -1,6 +1,6 @@ { "name": "codechecker-api", - "version": "6.51.0", + "version": "6.52.0", "description": "Generated node.js compatible API stubs for CodeChecker server.", "main": "lib", "homepage": "https://github.com/Ericsson/codechecker", diff --git a/web/api/products.thrift b/web/api/products.thrift index 835c9c00cb..b6b3cf1bda 100644 --- a/web/api/products.thrift +++ b/web/api/products.thrift @@ -33,7 +33,8 @@ struct ProductConfiguration { 5: optional DatabaseConnection connection, 6: i64 runLimit, 7: optional bool isReviewStatusChangeDisabled, - 8: optional Confidentiality confidentiality + 8: optional Confidentiality confidentiality, + 9: optional i64 reportLimit } typedef list ProductConfigurations @@ -53,7 +54,8 @@ struct Product { 11: i64 runLimit, // Number of allowed runs for this product. 12: list admins, // Administrators of this product. 13: list runStoreInProgress, // List of run names which are in progress. - 14: optional Confidentiality confidentiality // Confidentiality classification of the product + 14: optional Confidentiality confidentiality, // Confidentiality classification of the product. + 15: optional i64 reportLimit // Maximum number of reports allowed in a run. } typedef list Products diff --git a/web/api/py/codechecker_api/dist/codechecker_api.tar.gz b/web/api/py/codechecker_api/dist/codechecker_api.tar.gz index 1e7ab3752d..7a47790424 100644 Binary files a/web/api/py/codechecker_api/dist/codechecker_api.tar.gz and b/web/api/py/codechecker_api/dist/codechecker_api.tar.gz differ diff --git a/web/api/py/codechecker_api/setup.py b/web/api/py/codechecker_api/setup.py index 81feac51db..e873d11dd1 100644 --- a/web/api/py/codechecker_api/setup.py +++ b/web/api/py/codechecker_api/setup.py @@ -8,7 +8,7 @@ with open('README.md', encoding='utf-8', errors="ignore") as f: long_description = f.read() -api_version = '6.51.0' +api_version = '6.52.0' setup( name='codechecker_api', diff --git a/web/api/py/codechecker_api_shared/dist/codechecker_api_shared.tar.gz b/web/api/py/codechecker_api_shared/dist/codechecker_api_shared.tar.gz index 605bf4c25d..9831dc9bf8 100644 Binary files a/web/api/py/codechecker_api_shared/dist/codechecker_api_shared.tar.gz and b/web/api/py/codechecker_api_shared/dist/codechecker_api_shared.tar.gz differ diff --git a/web/api/py/codechecker_api_shared/setup.py b/web/api/py/codechecker_api_shared/setup.py index 0b3fd6d7a7..1e1bdcc8dd 100644 --- a/web/api/py/codechecker_api_shared/setup.py +++ b/web/api/py/codechecker_api_shared/setup.py @@ -8,7 +8,7 @@ with open('README.md', encoding='utf-8', errors="ignore") as f: long_description = f.read() -api_version = '6.51.0' +api_version = '6.52.0' setup( name='codechecker_api_shared', diff --git a/web/client/codechecker_client/cmd/cmd.py b/web/client/codechecker_client/cmd/cmd.py index e8c74600b6..c17b4da9aa 100644 --- a/web/client/codechecker_client/cmd/cmd.py +++ b/web/client/codechecker_client/cmd/cmd.py @@ -743,6 +743,14 @@ def __register_add(parser): help="A custom textual description to be shown " "alongside the product.") + parser.add_argument('--report-limit', + type=int, + dest="report_limit", + default=argparse.SUPPRESS, + required=False, + help="The maximum number of reports allowed to" + "to store in one go.") + dbmodes = parser.add_argument_group( "database arguments", "NOTE: These database arguments are relative to the server " diff --git a/web/client/codechecker_client/cmd/store.py b/web/client/codechecker_client/cmd/store.py index b0663bbb7d..67b6a70f73 100644 --- a/web/client/codechecker_client/cmd/store.py +++ b/web/client/codechecker_client/cmd/store.py @@ -37,6 +37,7 @@ SourceCodeCommentHandler from codechecker_client import client as libclient +from codechecker_client import product from codechecker_common import arg, logger, cmd_config from codechecker_common.checker_labels import CheckerLabels from codechecker_common.util import load_json @@ -415,7 +416,7 @@ def parse_analyzer_result_files( return analyzer_result_file_reports -def assemble_zip(inputs, zip_file, client, checker_labels: CheckerLabels): +def assemble_zip(inputs, zip_file, client, prod_client, checker_labels: CheckerLabels): """Collect and compress report and source files, together with files contanining analysis related information into a zip file which will be sent to the server. @@ -576,6 +577,28 @@ def assemble_zip(inputs, zip_file, client, checker_labels: CheckerLabels): # Print statistics what will be stored to the server. stats.write() + # Fail store early if too many reports. + p = prod_client.getCurrentProduct() + if stats.num_of_reports > p.reportLimit: + over_limit = stats.num_of_reports - p.reportLimit + LOG.error(f"""Report Limit Exceeded + +This report folder cannot be stored because the number of reports in the +result folder is too high. This measeure is in place to protect the server +from overloading. + +Please review the 'Checker Statistics' section of this output to gain a better +understanding of which checkers have generated an excessive number of reports. + +Disable checkers that have generated an excessive number of reports and then +rerun the analysis to be able to store the results on the server. + +* Configured report limit for this product: {p.reportLimit} +* Number of reports in the result folder: {stats.num_of_reports} +* The report folder exceeds the limit by {over_limit} reports + """) + sys.exit(1) + zip_size = os.stat(zip_file).st_size LOG.info("Compressing report zip file...") @@ -761,6 +784,8 @@ def main(args): # Setup connection to the remote server. client = libclient.setup_client(args.product_url) + protocol, host, port, product_name = product.split_product_url(args.product_url) + prod_client = libclient.setup_product_client(protocol, host, port, product_name=product_name) zip_file_handle, zip_file = tempfile.mkstemp('.zip') LOG.debug("Will write mass store ZIP to '%s'...", zip_file) @@ -770,7 +795,7 @@ def main(args): LOG.debug("Assembling zip file.") try: - assemble_zip(args.input, zip_file, client, context.checker_labels) + assemble_zip(args.input, zip_file, client, prod_client, context.checker_labels) except Exception as ex: print(ex) import traceback diff --git a/web/client/codechecker_client/product_client.py b/web/client/codechecker_client/product_client.py index 725cd6c372..618bb4b7dc 100644 --- a/web/client/codechecker_client/product_client.py +++ b/web/client/codechecker_client/product_client.py @@ -113,10 +113,15 @@ def handle_add_product(args): desc = convert.to_b64(args.description) \ if 'description' in args else None + report_limit = None + if hasattr(args, "report_limit") and args.report_limit: + report_limit = int(args.report_limit) + prod = ProductConfiguration( endpoint=args.endpoint, displayedName_b64=name, description_b64=desc, + reportLimit=report_limit, connection=dbc) LOG.debug("Sending request to add product...") diff --git a/web/client/codechecker_client/thrift_call.py b/web/client/codechecker_client/thrift_call.py index 0b55f646b9..5966786bf5 100644 --- a/web/client/codechecker_client/thrift_call.py +++ b/web/client/codechecker_client/thrift_call.py @@ -56,26 +56,33 @@ def wrapper(self, *args, **kwargs): except codechecker_api_shared.ttypes.RequestFailed as reqfailure: LOG.error('Calling API endpoint: %s', funcName) if reqfailure.errorCode ==\ - codechecker_api_shared.ttypes.ErrorCode.DATABASE: - LOG.error('Database error on server\n%s', - str(reqfailure.message)) - elif reqfailure.errorCode ==\ - codechecker_api_shared.ttypes.ErrorCode.AUTH_DENIED: - LOG.error('Authentication denied\n %s', - str(reqfailure.message)) - elif reqfailure.errorCode ==\ - codechecker_api_shared.ttypes.ErrorCode.UNAUTHORIZED: - LOG.error('Unauthorized to access\n %s', - str(reqfailure.message)) - LOG.error('Ask the product admin for additional access ' - 'rights.') - elif reqfailure.errorCode ==\ - codechecker_api_shared.ttypes.ErrorCode.API_MISMATCH: - LOG.error('Client/server API mismatch\n %s', - str(reqfailure.message)) + codechecker_api_shared.ttypes.ErrorCode.GENERAL and \ + reqfailure.extraInfo and \ + reqfailure.extraInfo[0] == "report_limit": + # We handle this error in near the business logic. + raise reqfailure else: - LOG.error('API call error: %s\n%s', funcName, str(reqfailure)) - sys.exit(1) + if reqfailure.errorCode ==\ + codechecker_api_shared.ttypes.ErrorCode.DATABASE: + LOG.error('Database error on server\n%s', + str(reqfailure.message)) + elif reqfailure.errorCode ==\ + codechecker_api_shared.ttypes.ErrorCode.AUTH_DENIED: + LOG.error('Authentication denied\n %s', + str(reqfailure.message)) + elif reqfailure.errorCode ==\ + codechecker_api_shared.ttypes.ErrorCode.UNAUTHORIZED: + LOG.error('Unauthorized to access\n %s', + str(reqfailure.message)) + LOG.error('Ask the product admin for additional access ' + 'rights.') + elif reqfailure.errorCode ==\ + codechecker_api_shared.ttypes.ErrorCode.API_MISMATCH: + LOG.error('Client/server API mismatch\n %s', + str(reqfailure.message)) + else: + LOG.error('API call error: %s\n%s', funcName, str(reqfailure)) + sys.exit(1) except TApplicationException as ex: LOG.error("Internal server error: %s", str(ex.message)) sys.exit(1) diff --git a/web/codechecker_web/shared/version.py b/web/codechecker_web/shared/version.py index 38b2ec42e4..60867c84d7 100644 --- a/web/codechecker_web/shared/version.py +++ b/web/codechecker_web/shared/version.py @@ -18,7 +18,7 @@ # The newest supported minor version (value) for each supported major version # (key) in this particular build. SUPPORTED_VERSIONS = { - 6: 51 + 6: 52 } # Used by the client to automatically identify the latest major and minor diff --git a/web/server/codechecker_server/api/mass_store_run.py b/web/server/codechecker_server/api/mass_store_run.py index 13c00f13c4..e251708095 100644 --- a/web/server/codechecker_server/api/mass_store_run.py +++ b/web/server/codechecker_server/api/mass_store_run.py @@ -18,6 +18,7 @@ from collections import defaultdict from datetime import datetime from hashlib import sha256 +from pathlib import Path from tempfile import TemporaryDirectory from typing import Any, Dict, List, Optional, Set, Tuple @@ -948,6 +949,53 @@ def get_missing_file_ids(report: Report) -> List[str]: return True + def __check_report_count(self, report_dir: Path): + """ + This method checks the number of reports in the given report, + Raises exception if the number of reports is more than the + that is configured for the product. + """ + # Read all plist files in report directory. + plist_files = list(report_dir.glob('**/*.plist')) + report_count = 0 + for plist_file in plist_files: + # The check_name tag only occurs exactly once per report. + # Don't parse plist, just count the number of + # check_name occurances. + unique_tag = b'check_name' + with plist_file.open('rb') as f: + plist_file_content = f.read() + report_count += plist_file_content.count(unique_tag) + + report_limit = 0 + with DBSession(self.__config_database) as session: + product = session.query(Product).get(self.__product.id) + if product.report_limit: + report_limit = product.report_limit + + if report_count > report_limit: + LOG.error("The number of reports in the given zip file is " + + "larger than the allowed." + + f"The limit is {report_limit}, you have: {report_count}!") + extra_info = [ + "report_limit", + f"limit:{report_limit}"), + f"report_count:{report_count}" + ] + raise codechecker_api_shared.ttypes.RequestFailed( + codechecker_api_shared.ttypes. + ErrorCode.GENERAL, + "**Warning: Excessive Reports in This Report Folder** " + + "There are too many reports in this report " + + "folder, which has triggered a server " + + "protection measure. To proceed, please " + + "reduce the number of reports to a maximum " + + f"of {report_limit}. **Current Reports:** {report_count} " + + f"**Excess Reports:** {report_count-report_limit} You can " + + "identify specific checkers to disable by using the " + + "`CodeChecker parse --summary ` command.", + extra_info) + def __store_reports( self, session: DBSession, @@ -1104,6 +1152,9 @@ def store(self) -> int: content_hash_file = os.path.join( zip_dir, 'content_hashes.json') + # Validate that the report count is less than it is allowed. + self.__check_report_count(Path(report_dir)) + filename_to_hash = load_json(content_hash_file, {}) with LogTask(run_name=self.__name, diff --git a/web/server/codechecker_server/api/product_server.py b/web/server/codechecker_server/api/product_server.py index ad11dad4f2..b0999168a4 100644 --- a/web/server/codechecker_server/api/product_server.py +++ b/web/server/codechecker_server/api/product_server.py @@ -129,6 +129,11 @@ def __get_product(self, session, product): confidentiality = \ confidentiality_enum(product.confidentiality) + if product.report_limit is not None: + report_limit = product.report_limit + else: + report_limit = None + return server_product, ttypes.Product( id=product.id, endpoint=product.endpoint, @@ -141,7 +146,8 @@ def __get_product(self, session, product): administrating=self.__administrating(args), databaseStatus=server_product.db_status, admins=[admin.name for admin in admins], - confidentiality=confidentiality) + confidentiality=confidentiality, + reportLimit=report_limit) @timeit def getPackageVersion(self): @@ -295,6 +301,7 @@ def getProductConfiguration(self, product_id): description_b64=descr, connection=dbc, runLimit=product.run_limit, + reportLimit=product.report_limit, isReviewStatusChangeDisabled=is_review_status_change_disabled, confidentiality=confidentiality) @@ -387,6 +394,7 @@ def addProduct(self, product): name=displayed_name, description=description, run_limit=product.runLimit, + report_limit=product.reportLimit, is_review_status_change_disabled=is_rws_change_disabled, confidentiality=confidentiality) @@ -583,6 +591,7 @@ def editProduct(self, product_id, new_config): # Update the settings in the database. product.endpoint = new_config.endpoint product.run_limit = new_config.runLimit + product.report_limit = new_config.reportLimit product.is_review_status_change_disabled = \ new_config.isReviewStatusChangeDisabled product.connection = conn_str diff --git a/web/server/codechecker_server/database/config_db_model.py b/web/server/codechecker_server/database/config_db_model.py index 48c94f9115..376162d90c 100644 --- a/web/server/codechecker_server/database/config_db_model.py +++ b/web/server/codechecker_server/database/config_db_model.py @@ -39,6 +39,7 @@ class Product(Base): display_name = Column(String, nullable=False) description = Column(Text) run_limit = Column(Integer) + report_limit = Column(Integer, nullable=False, server_default="500000") num_of_runs = Column(Integer, server_default="0") latest_storage_date = Column(DateTime, nullable=True) @@ -49,13 +50,15 @@ class Product(Base): confidentiality = Column(String, nullable=True) def __init__(self, endpoint, conn_str, name=None, description=None, - run_limit=None, is_review_status_change_disabled=False, + run_limit=None, report_limit=500000, + is_review_status_change_disabled=False, confidentiality=None): self.endpoint = endpoint self.connection = conn_str self.display_name = name if name else endpoint self.description = description self.run_limit = run_limit + self.report_limit = report_limit self.is_review_status_change_disabled = \ True if is_review_status_change_disabled else False self.confidentiality = confidentiality diff --git a/web/server/codechecker_server/migrations/config/versions/00099e8bc212_store_limit.py b/web/server/codechecker_server/migrations/config/versions/00099e8bc212_store_limit.py new file mode 100644 index 0000000000..22b459797f --- /dev/null +++ b/web/server/codechecker_server/migrations/config/versions/00099e8bc212_store_limit.py @@ -0,0 +1,28 @@ +"""Store limit + +Revision ID: 00099e8bc212 +Revises: 7829789fc19c +Create Date: 2023-03-10 16:45:19.301602 + +""" + +# revision identifiers, used by Alembic. +revision = '00099e8bc212' +down_revision = '7829789fc19c' +branch_labels = None +depends_on = None + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('products', sa.Column('report_limit', sa.Integer(), server_default='500000', nullable=False)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('products', 'report_limit') + # ### end Alembic commands ### diff --git a/web/server/vue-cli/package-lock.json b/web/server/vue-cli/package-lock.json index 1a021f4756..91b9584e16 100644 --- a/web/server/vue-cli/package-lock.json +++ b/web/server/vue-cli/package-lock.json @@ -11,7 +11,7 @@ "@mdi/font": "^6.5.95", "chart.js": "^2.9.4", "chartjs-plugin-datalabels": "^0.7.0", - "codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.51.0.tgz", + "codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.52.0.tgz", "codemirror": "^5.65.0", "date-fns": "^2.28.0", "js-cookie": "^3.0.1", @@ -4869,9 +4869,9 @@ } }, "node_modules/codechecker-api": { - "version": "6.51.0", - "resolved": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.51.0.tgz", - "integrity": "sha512-vfCOBJV1tZBok+F60qWRkkQc47d8Jsym/fp2m0qZoRy5ur2owGZxdJqBHzjWeSzuhqvsODO2aOGMCBHA74S5Dw==", + "version": "6.52.0", + "resolved": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.52.0.tgz", + "integrity": "sha512-LZfqs+NI5f9omIWjoBVhqD0TWvElw0V7LmJmNFWbUW1hWv4/+jC4fhhcVJcIvnERNVvlNb3NBi81IdTHtsXkdw==", "license": "SEE LICENSE IN LICENSE", "dependencies": { "thrift": "0.13.0-hotfix.1" @@ -19853,8 +19853,8 @@ "dev": true }, "codechecker-api": { - "version": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.51.0.tgz", - "integrity": "sha512-vfCOBJV1tZBok+F60qWRkkQc47d8Jsym/fp2m0qZoRy5ur2owGZxdJqBHzjWeSzuhqvsODO2aOGMCBHA74S5Dw==", + "version": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.52.0.tgz", + "integrity": "sha512-LZfqs+NI5f9omIWjoBVhqD0TWvElw0V7LmJmNFWbUW1hWv4/+jC4fhhcVJcIvnERNVvlNb3NBi81IdTHtsXkdw==", "requires": { "thrift": "0.13.0-hotfix.1" } diff --git a/web/server/vue-cli/package.json b/web/server/vue-cli/package.json index 12c46d9c53..bb5c56ebed 100644 --- a/web/server/vue-cli/package.json +++ b/web/server/vue-cli/package.json @@ -6,7 +6,7 @@ "private": true, "scripts": { "build": "rimraf dist && cross-env NODE_ENV=production webpack --config config/webpack.prod.js", - "server:codechecker": "CodeChecker server -w e2e/__codechecker/workspace > e2e/__codechecker/server.log", + "server:codechecker": "CodeChecker server --port 8002 -w e2e/__codechecker/workspace > e2e/__codechecker/server.log", "server:dev": "cross-env NODE_ENV=development webpack server --hot --progress --config config/webpack.dev.js", "server:prod": "http-server dist -c-1 --cors", "server": "npm run server:dev", @@ -27,7 +27,7 @@ }, "dependencies": { "@mdi/font": "^6.5.95", - "codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.51.0.tgz", + "codechecker-api": "file:../../api/js/codechecker-api-node/dist/codechecker-api-6.52.0.tgz", "chart.js": "^2.9.4", "chartjs-plugin-datalabels": "^0.7.0", "codemirror": "^5.65.0", diff --git a/web/server/vue-cli/src/components/Product/ProductConfigForm.vue b/web/server/vue-cli/src/components/Product/ProductConfigForm.vue index 09142a126b..57849f3776 100644 --- a/web/server/vue-cli/src/components/Product/ProductConfigForm.vue +++ b/web/server/vue-cli/src/components/Product/ProductConfigForm.vue @@ -39,6 +39,26 @@ @input="productConfig.runLimit = $event || null" /> + + + + + Maximum number of reports allowed to store in one go, + if exceeded, the store action will be rejected. + + + @@ -196,6 +216,9 @@ export default { ], runLimit: [ v => (!v || !!v && !isNaN(parseInt(v))) || "Number is required" + ], + reportLimit: [ + v => (!v || !!v && !isNaN(parseInt(v))) || "Number is required" ] }, }; diff --git a/web/tests/functional/store/__init__.py b/web/tests/functional/store/__init__.py index 7bf23a3bf8..99d6ac678d 100644 --- a/web/tests/functional/store/__init__.py +++ b/web/tests/functional/store/__init__.py @@ -34,13 +34,19 @@ def setup_package(): 'workspace': TEST_WORKSPACE, 'checkers': [], 'reportdir': os.path.join(TEST_WORKSPACE, 'reports'), - 'test_project': 'store_test' + 'test_project': 'store_test', + 'test_project_2': 'store_limited_product' } # Start or connect to the running CodeChecker server and get connection # details. print("This test uses a CodeChecker server... connecting...") server_access = codechecker.start_or_get_server() + + server_access['viewer_product'] = 'store_limited_product' + codechecker.add_test_package_product(server_access, TEST_WORKSPACE, + report_limit=2) + server_access['viewer_product'] = 'store_test' codechecker.add_test_package_product(server_access, TEST_WORKSPACE) @@ -75,6 +81,10 @@ def teardown_package(): check_env = env.import_test_cfg(TEST_WORKSPACE)[ 'codechecker_cfg']['check_env'] codechecker.remove_test_package_product(TEST_WORKSPACE, check_env) + codechecker.remove_test_package_product( + TEST_WORKSPACE, + check_env, + product="store_limited_product") print("Removing: " + TEST_WORKSPACE) shutil.rmtree(TEST_WORKSPACE, ignore_errors=True) diff --git a/web/tests/functional/store/test_store.py b/web/tests/functional/store/test_store.py index d8ae978a78..d0414b24ee 100644 --- a/web/tests/functional/store/test_store.py +++ b/web/tests/functional/store/test_store.py @@ -53,7 +53,11 @@ def setUp(self): print("Running " + test_class + " tests in " + self._test_workspace) self._test_cfg = env.import_test_cfg(self._test_workspace) + self._codechecker_cfg = self._test_cfg["codechecker_cfg"] + + print("This is the config ", self._codechecker_cfg) + self._test_directory = os.path.dirname(os.path.abspath(inspect.getfile( inspect.currentframe()))) self._temp_workspace = os.path.join(self._codechecker_cfg["workspace"], @@ -68,13 +72,20 @@ def setUp(self): self.product_name = self._codechecker_cfg['viewer_product'] + self.limited_product_name = self._codechecker_cfg['test_project_2'] + # Setup a viewer client to test viewer API calls. self._cc_client = env.setup_viewer_client(self._test_workspace) self.assertIsNotNone(self._cc_client) self._pr_client = env.setup_product_client( self._test_workspace, product=self.product_name) + + self._limited_pr_client = env.setup_product_client( + self._test_workspace, product=self.limited_product_name) + self.assertIsNotNone(self._pr_client) + self.assertIsNotNone(self._limited_pr_client) def test_product_details(self): """ @@ -392,3 +403,21 @@ def analyze_tidy(cfg): reports = json.loads(out) self.assertEqual(len(reports), 2) + + def test_store_limit(self): + """ + Test store limit of a product. + """ + + run_name = "limit_test" + store_cmd = [ + env.codechecker_cmd(), "store", + self._divide_zero_workspace, + "--name", run_name, + "--url", env.parts_to_url(self._codechecker_cfg, 'test_project_2'), + "--trim-path-prefix", self._divide_zero_workspace, + "--verbose", "debug", + ] + + _, out, _ = _call_cmd(store_cmd) + self.assertIn("There are too many reports in this report folder!", out) diff --git a/web/tests/libtest/codechecker.py b/web/tests/libtest/codechecker.py index ca9a4c2846..9a383dfa33 100644 --- a/web/tests/libtest/codechecker.py +++ b/web/tests/libtest/codechecker.py @@ -711,7 +711,7 @@ def start_server_proc(event, server_cmd, checking_env): def add_test_package_product(server_data, test_folder, check_env=None, - protocol='http', + protocol='http', report_limit=None, user_permissions=DEFAULT_USER_PERMISSIONS): """ Add a product for a test suite to the server provided by server_data. @@ -739,6 +739,8 @@ def add_test_package_product(server_data, test_folder, check_env=None, '--name', os.path.basename(test_folder), '--description', "Automatically created product for test.", '--verbose', 'debug'] + if report_limit: + add_command.extend(['--report-limit', str(report_limit)]) # If tests are running on postgres, we need to create a database. pg_config = env.get_postgresql_cfg() @@ -800,7 +802,8 @@ def add_test_package_product(server_data, test_folder, check_env=None, raise Exception("Failed to add the product to the test server!") -def remove_test_package_product(test_folder, check_env=None, protocol='http'): +def remove_test_package_product(test_folder, check_env=None, protocol='http', + product=None): """ Remove the product associated with the given test folder. The folder must exist, as the server configuration is read from the folder. @@ -811,6 +814,7 @@ def remove_test_package_product(test_folder, check_env=None, protocol='http'): server_data = env.import_test_cfg(test_folder)['codechecker_cfg'] print(server_data) + product_to_remove = product if product else server_data['viewer_product'] if 'check_env' not in server_data: server_data['check_env'] = check_env @@ -821,12 +825,11 @@ def remove_test_package_product(test_folder, check_env=None, protocol='http'): str(server_data['viewer_port']), '') del_command = ['CodeChecker', 'cmd', 'products', 'del', - server_data['viewer_product'], - '--url', url] + product_to_remove, '--url', url] print(' '.join(del_command)) - # Authenticate as SUPERUSER to be able to create the product. + # Authenticate as SUPERUSER to be able to delete the product. login(server_data, test_folder, "root", "root", protocol) returncode = subprocess.call( del_command, @@ -839,7 +842,7 @@ def remove_test_package_product(test_folder, check_env=None, protocol='http'): # SQLite databases are deleted automatically as part of the # workspace removal. if env.get_postgresql_cfg(): - env.del_database(server_data['viewer_product'], check_env) + env.del_database(product_to_remove, check_env) if returncode: raise Exception("Failed to remove the product from the test server!") diff --git a/web/tests/libtest/env.py b/web/tests/libtest/env.py index ec5e932028..b6642771fc 100644 --- a/web/tests/libtest/env.py +++ b/web/tests/libtest/env.py @@ -256,13 +256,13 @@ def get_run_names(workspace): return import_test_cfg(workspace)['codechecker_cfg']['run_names'] -def parts_to_url(codechecker_cfg): +def parts_to_url(codechecker_cfg, product='viewer_product'): """ Creates a product URL string from the test configuration dict. """ return codechecker_cfg['viewer_host'] + ':' + \ str(codechecker_cfg['viewer_port']) + '/' + \ - codechecker_cfg['viewer_product'] + codechecker_cfg[product] def get_workspace(test_id='test'):