Exemplo n.º 1
0
    def evaluate(self):
        element, namespaces = self.element, self.namespaces
        xpath, tags = self.xpath, self.tags

        # This may raise XPathEvalError for incorrect namespacing
        results = element.xpath(xpath, namespaces=namespaces)

        # xpath does not exist in XML
        if not results:
            self.message = "xpath: `{}` does not" " exist in the XML.".format(
                xpath)
            return False

        # xpath exists, no tag lookup -> Pass
        if not tags:
            self.message = "xpath: `{}` exists in the XML.".format(xpath)
            return True

        data = []

        # Tag lookup in xpath
        for idx, tag in enumerate(tags):
            try:
                text = results[idx].text

                if not text:
                    xml_comp = XMLTagComparison(
                        tag=tag,
                        diff=None,
                        error="No value is found,"
                        " although the path exists.",
                        extra=None,
                    )
                elif isinstance(tag, str) and re.match(tag, text):
                    extra = tag if tag != text else None
                    xml_comp = XMLTagComparison(tag=text,
                                                diff=None,
                                                error=None,
                                                extra=extra)
                else:
                    passed, error = comparison.basic_compare(first=text,
                                                             second=tag)

                    if error:
                        xml_comp = XMLTagComparison(tag=text,
                                                    diff=None,
                                                    error=error,
                                                    extra=tag)

                    elif not passed:
                        xml_comp = XMLTagComparison(tag=text,
                                                    diff=tag,
                                                    error=None,
                                                    extra=None)

                    else:
                        xml_comp = XMLTagComparison(tag=text,
                                                    diff=None,
                                                    error=None,
                                                    extra=tag)

            except IndexError:
                xml_comp = XMLTagComparison(
                    tag=None,
                    diff=tag,
                    error="No tags found for the index: {}".format(idx),
                    extra=None,
                )
            data.append(xml_comp)

        self.data = data
        return all([comp.passed for comp in self.data])
Exemplo n.º 2
0
def compare_rows(
    table,
    expected_table,
    comparison_columns,
    display_columns,
    strict=True,
    fail_limit=0,
    report_fails_only=False,
):
    """
    Apply row by row comparison of two tables,
    creating a ``RowComparison`` for each row couple.

    :param table: Original table.
    :type table: ``list`` of ``dict``
    :param expected_table: Comparison table, it can contain
                           custom comparators as column values.
    :type expected_table: ``list`` of ``dict``
    :param comparison_columns: Columns to be used for comparison.
    :type comparison_columns: ``list`` of ``str``
    :param display_columns: Columns to be used
                            for populating ``RowComparison`` data.
    :type display_columns: ``list`` of ``str``
    :param strict: Custom comparator strictness flag, currently will
                   auto-convert non-str values to
                   ``str`` for pattern if ``False``.
    :type strict: ``bool``
    :param fail_limit: Max number of failures before aborting
                       the comparison run. Useful for large
                       tables, when we want to stop after we have N rows
                       that fail the comparison.
    :type fail_limit: ``int``
    :param report_fails_only: If ``True``, only repoty the failures (used
                              for diff typically)
    :type report_fails_only: ``bool``
    :returns: overall passed status and RowComparison data.
    """

    # We always want to display a superset of comparison columns
    # otherwise we can have a failing comparison but the
    # resulting data will not include the mismatch context.
    if not set(comparison_columns).issubset(display_columns):
        raise ValueError("comparison_columns ({}) must be "
                         "subset of display_columns ({})".format(
                             ", ".join(sorted(comparison_columns)),
                             ", ".join(sorted(display_columns)),
                         ))

    data = []
    num_failures = 0
    display_only = [
        col for col in display_columns if col not in comparison_columns
    ]

    for idx, (row_1, row_2) in enumerate(zip(table, expected_table)):
        diff, errors, extra = {}, {}, {}

        for column_name in comparison_columns:
            if column_name not in row_1 and column_name not in row_2:
                continue
            elif (column_name in row_1 and column_name not in row_2
                  or column_name not in row_1 and column_name in row_2):
                diff[column_name] = row_2.get(column_name, None)
                continue

            first, second = row_1[column_name], row_2[column_name]

            passed, error = comparison.basic_compare(first=first,
                                                     second=second,
                                                     strict=strict)

            if error:
                errors[column_name] = error

            elif not passed:
                diff[column_name] = second

            # Populate extra if values differ (we don't check for equality as
            # that may have raised an error for incompatible types as well)
            if first is not second and (error or passed):
                extra[column_name] = second

        row_data = [row_1.get(col, None) for col in display_columns]

        # Need to populate `extra` with values from the second table
        # they are not being used for comparison but for display.
        extra.update({col: row_2.get(col, None) for col in display_only})

        row_comparison = RowComparison(idx, row_data, diff, errors, extra)

        if not (report_fails_only and row_comparison.passed):
            data.append(row_comparison)

        if not row_comparison.passed:
            num_failures += 1

        if fail_limit > 0 and num_failures >= fail_limit:
            break

    return num_failures == 0, data
Exemplo n.º 3
0
def compare_rows(table,
                 expected_table,
                 comparison_columns,
                 display_columns,
                 strict=True,
                 fail_limit=0):
    """
      Apply row by row comparison of two tables,
      creating a ``RowComparison`` for each row couple.

      :param table: Original table.
      :type table: ``list`` of ``dict``
      :param expected_table: Comparison table, it can contain
                            custom comparators as column values.
      :type expected_table: ``list`` of ``dict``
      :param comparison_columns: Columns to be used for comparison.
      :type comparison_columns: ``list`` of ``str``
      :param display_columns: Columns to be used
                            for populating ``RowComparison`` data.
      :type display_columns: ``list`` of ``str``
      :param strict: Custom comparator strictness flag, currently will
                     auto-convert non-str values to
                      ``str`` for pattern if ``False``.
      :type strict: ``bool``
      :param fail_limit: Max number of failures before aborting
                        the comparison run. Useful for large
                        tables, when we want to stop after we have N rows
                        that fail the comparison. The result will contain
                         only failing comparisons if this argument is nonzero.
      :type fail_limit: ``int``
      :returns: overall passed status and RowComparison data.
    """

    # We always want to display a superset of comparison columns
    # otherwise we can have a failing comparison but the
    # resulting data will not include the mismatch context.
    if not set(comparison_columns).issubset(display_columns):
        raise ValueError('comparison_columns ({}) must be '
                         'subset of display_columns ({})'.format(
                             ', '.join(sorted(comparison_columns)),
                             ', '.join(sorted(display_columns))))

    data = []
    num_failures = 0
    display_only = [
        col for col in display_columns if col not in comparison_columns
    ]

    for idx, (row_1, row_2) in enumerate(zip(table, expected_table)):
        diff, errors, extra = {}, {}, {}

        for column_name in comparison_columns:
            first, second = row_1[column_name], row_2[column_name]

            passed, error = comparison.basic_compare(first=first,
                                                     second=second,
                                                     strict=strict)

            if error:
                errors[column_name] = error

            elif not passed:
                diff[column_name] = second

            # Populate extra if values differ (we don't check for equality
            # as that may have raised an error for incompatible types as well
            if first is not second and (error or passed):
                extra[column_name] = second

        row_data = [row_1[col] for col in display_columns]

        # Need to populate extra with values from the
        # second table, if they are not being used
        # for comparison but have different values.
        extra.update({
            col: row_2[col]
            for col in display_only
            if col in row_2 and row_2[col] != row_1[col]
        })

        row_comparison = RowComparison(idx, row_data, diff, errors, extra)

        if not row_comparison.passed:
            num_failures += 1

        # Include only failing comparisons if there is a limit
        if fail_limit > 0:

            if not row_comparison.passed:
                data.append(row_comparison)

            if num_failures >= fail_limit:
                break

        else:
            data.append(row_comparison)

    return num_failures == 0, data