# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions andsss
# limitations under the License.
"""Tests for coverage_data_utils.py"""
from unittest import mock

import pandas as pd
import pandas.testing as pd_test

from analysis import coverage_data_utils

FUZZER = 'afl'
BENCHMARK = 'libpng-1.6.38'
EXPERIMENT_FILESTORE_PATH = 'gs://fuzzbench-data/myexperiment'
SAMPLE_DF = pd.DataFrame([{
    'experiment_filestore': 'gs://fuzzbench-data',
    'experiment': 'exp1',
    'fuzzer': FUZZER,
    'benchmark': BENCHMARK
}, {
    'experiment_filestore': 'gs://fuzzbench-data2',
    'experiment': 'exp2',
    'fuzzer': 'libfuzzer',
    'benchmark': BENCHMARK
}])


def create_coverage_data():
    """Utility function to create test data."""
    return {
        'afl libpng-1.6.38': [[0, 0, 1, 1], [0, 0, 2, 2], [0, 0, 3, 3]],
        'libfuzzer libpng-1.6.38': [[0, 0, 1, 1], [0, 0, 2, 3], [0, 0, 3, 3],
                                    [0, 0, 4, 4]]
    }


def test_get_unique_branch_dict():
    """Tests get_unique_branch_dict() function."""
    coverage_dict = create_coverage_data()
    benchmark_coverage_dict = coverage_data_utils.get_benchmark_cov_dict(
        coverage_dict, 'libpng-1.6.38')
    unique_branch_dict = coverage_data_utils.get_unique_branch_dict(
        benchmark_coverage_dict)
    expected_dict = {
        (0, 0, 2, 2): ['afl'],
        (0, 0, 2, 3): ['libfuzzer'],
        (0, 0, 4, 4): ['libfuzzer']
    }
    assert expected_dict == unique_branch_dict


def test_get_unique_branch_cov_df():
    """Tests get_unique_branch_cov_df() function."""
    coverage_dict = create_coverage_data()
    benchmark_coverage_dict = coverage_data_utils.get_benchmark_cov_dict(
        coverage_dict, 'libpng-1.6.38')
    unique_branch_dict = coverage_data_utils.get_unique_branch_dict(
        benchmark_coverage_dict)
    fuzzer_names = ['afl', 'libfuzzer']
    unique_branch_df = coverage_data_utils.get_unique_branch_cov_df(
        unique_branch_dict, fuzzer_names)
    unique_branch_df = unique_branch_df.sort_values(by=['fuzzer']).reset_index(
        drop=True)
    expected_df = pd.DataFrame([{
        'fuzzer': 'afl',
        'unique_branches_covered': 1
    }, {
        'fuzzer': 'libfuzzer',
        'unique_branches_covered': 2
    }])
    assert unique_branch_df.equals(expected_df)


def test_get_benchmark_cov_dict():
    """Tests that get_benchmark_cov_dict() returns correct dictionary."""
    coverage_dict = create_coverage_data()
    benchmark = 'libpng-1.6.38'
    benchmark_cov_dict = coverage_data_utils.get_benchmark_cov_dict(
        coverage_dict, benchmark)
    expected_cov_dict = {
        'afl': {(0, 0, 3, 3), (0, 0, 2, 2), (0, 0, 1, 1)},
        'libfuzzer': {(0, 0, 4, 4), (0, 0, 3, 3), (0, 0, 2, 3), (0, 0, 1, 1)}
    }
    assert expected_cov_dict == benchmark_cov_dict


def test_get_pairwise_unique_coverage_table():
    """Tests that get_pairwise_unique_coverage_table() gives the
    correct dataframe."""
    coverage_dict = create_coverage_data()
    benchmark_coverage_dict = coverage_data_utils.get_benchmark_cov_dict(
        coverage_dict, 'libpng-1.6.38')
    fuzzers = ['libfuzzer', 'afl']
    table = coverage_data_utils.get_pairwise_unique_coverage_table(
        benchmark_coverage_dict, fuzzers)
    expected_table = pd.DataFrame([[0, 1], [2, 0]],
                                  index=fuzzers,
                                  columns=fuzzers)
    pd_test.assert_frame_equal(table, expected_table)


def test_get_fuzzer_benchmark_covered_branches_filestore_path():
    """Tests that get_fuzzer_benchmark_covered_branches_filestore_path returns
    the correct result."""
    assert (coverage_data_utils.
            get_fuzzer_benchmark_covered_branches_filestore_path(
                FUZZER, BENCHMARK, EXPERIMENT_FILESTORE_PATH) == (
                    'gs://fuzzbench-data/myexperiment/'
                    'coverage/data/libpng-1.6.38/afl/'
                    'covered_branches.json'))


def test_fuzzer_and_benchmark_to_key():
    """Tests that fuzzer_and_benchmark_to_key returns the correct result."""
    assert (coverage_data_utils.fuzzer_and_benchmark_to_key(
        FUZZER, BENCHMARK) == 'afl libpng-1.6.38')


def test_key_to_fuzzer_and_benchmark():
    """Tests that key_to_fuzzer_and_benchmark returns the correct result."""
    assert (coverage_data_utils.key_to_fuzzer_and_benchmark('afl libpng-1.6.38')
            == (FUZZER, BENCHMARK))


def test_fuzzer_benchmark_key_roundtrip():
    """Tests that key_to_fuzzer_and_benchmark(fuzzer_and_benchmark_to_key(X, Y))
    returns (X, Y)."""
    assert (coverage_data_utils.key_to_fuzzer_and_benchmark(
        coverage_data_utils.fuzzer_and_benchmark_to_key(
            FUZZER, BENCHMARK)) == (FUZZER, BENCHMARK))


def test_get_experiment_filestore_path_for_fuzzer_benchmark():
    """Tests that get_experiment_filestore_path_for_fuzzer_benchmark returns the
    right result."""
    filestore_path = (
        coverage_data_utils.get_experiment_filestore_path_for_fuzzer_benchmark(
            FUZZER, BENCHMARK, SAMPLE_DF))
    assert filestore_path == 'gs://fuzzbench-data/exp1'


@mock.patch('analysis.coverage_data_utils.logger.warning')
def test_get_experiment_filestore_path_for_fuzzer_benchmark_multiple(
        mocked_warning):
    """Tests that get_experiment_filestore_path_for_fuzzer_benchmark returns the
    right result when there are multiple filestores for a single pair and that a
    warning is logged.."""
    df = pd.DataFrame([{
        'experiment_filestore': 'gs://fuzzbench-data',
        'experiment': 'exp1',
        'fuzzer': FUZZER,
        'benchmark': BENCHMARK
    }, {
        'experiment_filestore': 'gs://fuzzbench-data2',
        'experiment': 'exp2',
        'fuzzer': FUZZER,
        'benchmark': BENCHMARK
    }])
    filestore_path = (
        coverage_data_utils.get_experiment_filestore_path_for_fuzzer_benchmark(
            FUZZER, BENCHMARK, df))
    assert filestore_path in ('gs://fuzzbench-data/exp1',
                              'gs://fuzzbench-data2/exp2')

    assert mocked_warning.call_count == 1


def test_get_experiment_filestore_paths():
    """Tests that get_experiment_filestore_paths returns the right result."""
    df = pd.DataFrame([{
        'experiment_filestore': 'gs://fuzzbench-data',
        'experiment': 'exp1'
    }, {
        'experiment_filestore': 'gs://fuzzbench-data2',
        'experiment': 'exp2'
    }])
    assert sorted(coverage_data_utils.get_experiment_filestore_paths(df)) == [
        'gs://fuzzbench-data/exp1', 'gs://fuzzbench-data2/exp2'
    ]


def test_coverage_report_filestore_path():
    """Tests that get_coverage_report_filestore_path returns the correct
    result."""
    expected_cov_report_url = ('gs://fuzzbench-data/exp1/coverage/reports/'
                               'libpng-1.6.38/afl/index.html')
    assert coverage_data_utils.get_coverage_report_filestore_path(
        FUZZER, BENCHMARK, SAMPLE_DF) == expected_cov_report_url
