Commit cbe4fa3e authored by Luke Mewburn's avatar Luke Mewburn
Browse files

compat_asn1: support optional dependencies

compat_asn1:
- Allow supplying of dependency files to load with FILE1 and FILE2.
- Rework compare_files() to require a_files and b_files as lists of paths.
  This simplifies the caller's ability to supply optional dependencies.
- Invoke pycrate_asn1c compile_text() with the list of ASN.1 text
  and the list of filenames.
- Deprecate fa_path and fb_path in Context.
- Deprecate compare_asntext().

check_compat_asn1:
- Supply IPPR dependencies for r18 and r19 TS33128Payloads.
parent 4eda1625
Loading
Loading
Loading
Loading
+20 −13
Original line number Diff line number Diff line
@@ -12,12 +12,14 @@ import compat_asn1
def compare_releases(files):
    total_errors = 0
    for idx in range(len(files) - 1):
        ctx = compat_asn1.Context(fa_path=files[idx], fb_path=files[idx + 1])
        compat_asn1.compare_files(ctx=ctx)
        a_files = files[idx]
        b_files = files[idx + 1]
        ctx = compat_asn1.Context()
        compat_asn1.compare_files(ctx=ctx, a_files=a_files, b_files=b_files)
        if ctx.errors:
            print("-----------------------------")
            print(f"File 1: {ctx.fa_path}")
            print(f"File 2: {ctx.fb_path}")
            print(f"File 1: {a_files[0]}")
            print(f"File 2: {b_files[0]}")
            print("Errors:")
            for error in ctx.errors:
                print(f"  {error}")
@@ -33,22 +35,27 @@ def main():

    error_count = 0

    ippr_deps = [
        "testing/dependencies/asn/stubs/LI-PS-PDU.asn",
        "testing/dependencies/asn/IPAccessPDU.asn",
    ]

    error_count += compare_releases(
        [
            "33128/r15/TS33128Payloads.asn",
            "33128/r16/TS33128Payloads.asn",
            "33128/r17/TS33128Payloads.asn",
            "33128/r18/TS33128Payloads.asn",
            "33128/r19/TS33128Payloads.asn",
            ["33128/r15/TS33128Payloads.asn"],
            ["33128/r16/TS33128Payloads.asn"],
            ["33128/r17/TS33128Payloads.asn"],
            ["33128/r18/TS33128Payloads.asn"] + ippr_deps,
            ["33128/r19/TS33128Payloads.asn"] + ippr_deps,
        ]
    )

    error_count += compare_releases(
        [
            "33128/r16/TS33128IdentityAssociation.asn",
            "33128/r17/TS33128IdentityAssociation.asn",
            "33128/r18/TS33128IdentityAssociation.asn",
            "33128/r19/TS33128IdentityAssociation.asn",
            ["33128/r16/TS33128IdentityAssociation.asn"],
            ["33128/r17/TS33128IdentityAssociation.asn"],
            ["33128/r18/TS33128IdentityAssociation.asn"],
            ["33128/r19/TS33128IdentityAssociation.asn"],
        ]
    )

+28 −29
Original line number Diff line number Diff line
@@ -25,8 +25,6 @@ TYPE_CONTAINER_OF = (pa_asnobj.TYPE_SEQ_OF, pa_asnobj.TYPE_SET_OF)

@dataclasses.dataclass
class Context:
    fa_path: str
    fb_path: str
    errors: list[str] = dataclasses.field(default_factory=list)
    fa_mod: pa_asnobj.ASN1Dict | None = None
    fb_mod: pa_asnobj.ASN1Dict | None = None
@@ -47,10 +45,15 @@ def dict_swap(dictionary: dict) -> dict:
    return {value: key for key, value in dictionary.items()}


def parse_asn1(asntext: str) -> pa_asnobj.ASN1Dict:
    """Parse `asntext` as ASN.1 using pycrate, returning the module object."""
def parse_asn1(asn_files: list[str]) -> pa_asnobj.ASN1Dict:
    """Parse `asn_files` as ASN.1 using pycrate, returning the module object."""
    logging.info(f"Parsing {' '.join(asn_files)}")
    asn_text = []
    for filename in asn_files:
        filepath = pathlib.Path(filename)
        asn_text.append(filepath.read_text())
    pycrate_asn1c.asnproc.GLOBAL.clear()
    pycrate_asn1c.asnproc.compile_text(asntext)
    pycrate_asn1c.asnproc.compile_text(asn_text, filenames=asn_files)
    res = copy.deepcopy(pycrate_asn1c.asnproc.GLOBAL.MOD)
    return res

@@ -329,9 +332,9 @@ def compare_asntype(
        logging.debug(f"Unimplemented check of type '{fa_type.TYPE}'")


def compare_asntext(ctx: Context, fa_asntext: str, fb_asntext: str) -> None:
    """Compare ASN.1 text `fa_asntext` and `fb_asntext` for backwards compatibility
    issues introduced in `fb_asntext`:
def compare_files(ctx: Context, a_files: list[str], b_files: list[str]) -> None:
    """Compare ASN.1 in files `a_files` with files `b_files` for
    backwards compatibility issues:
    - Module is the same
      - Each type in the module:
         - Exists.
@@ -343,13 +346,10 @@ def compare_asntext(ctx: Context, fa_asntext: str, fb_asntext: str) -> None:

    Updates ctx.errors with a list of error messages (if any).
    """
    logging.info(f"Comparing file {ctx.fa_path} with {ctx.fb_path}")
    logging.info(f"Comparing file {a_files[0]} with {b_files[0]}")

    logging.info(f"Parsing {ctx.fa_path}")
    ctx.fa_mod = parse_asn1(fa_asntext)

    logging.info(f"Parsing {ctx.fb_path}")
    ctx.fb_mod = parse_asn1(fb_asntext)
    ctx.fa_mod = parse_asn1(a_files)
    ctx.fb_mod = parse_asn1(b_files)

    for module_name, fa_module in ctx.fa_mod.items():
        if module_name.startswith("_"):
@@ -358,7 +358,7 @@ def compare_asntext(ctx: Context, fa_asntext: str, fb_asntext: str) -> None:

        logging.info(f"Checking module {module_name}")
        if module_name not in ctx.fb_mod:
            process_error(ctx, f"Module {module_name} not present in {ctx.fb_path}")
            process_error(ctx, f"Module {module_name} not present")
            continue
        fb_module = ctx.fb_mod[module_name]

@@ -367,34 +367,33 @@ def compare_asntext(ctx: Context, fa_asntext: str, fb_asntext: str) -> None:
            logging.info(f"Checking type {repr_type_name(fa_type)}")

            if type_name not in fb_module:
                process_error(ctx, f"Type {type_name} not present in {ctx.fb_path}")
                process_error(ctx, f"Type {type_name} not present")
                continue
            fb_type = fb_module[type_name]

            compare_asntype(ctx=ctx, fa_type=fa_type, fb_type=fb_type)


def compare_files(ctx: Context) -> None:
    """Read ASN.1 files `ctx.fa_path` and `ctx.fb_path` and compare with compare_asntext."""
    fa_text = pathlib.Path(ctx.fa_path).read_text()
    fb_text = pathlib.Path(ctx.fb_path).read_text()
    compare_asntext(ctx=ctx, fa_asntext=fa_text, fb_asntext=fb_text)


def main():
    """standalone main."""
    loglevel = os.environ.get("LOGLEVEL", "WARNING").upper()
    logging.basicConfig(level=loglevel)
    pycrate_asn1c.utils.logger.setLevel(loglevel)

    if len(sys.argv) != 3:
        raise RuntimeError(f"Usage: {sys.argv[0]} FILE1.ASN FILE2.ASN")
    ctx = Context(fa_path=sys.argv[1], fb_path=sys.argv[2])
    compare_files(ctx=ctx)
    if len(sys.argv) < 3:
        raise RuntimeError(f"Usage: {sys.argv[0]} FILE1.ASN FILE2.ASN [DEP.ASN ...]")
    a_files = [sys.argv[1]]
    b_files = [sys.argv[2]]
    a_files.extend(sys.argv[3:])
    b_files.extend(sys.argv[3:])

    ctx = Context()
    compare_files(ctx=ctx, a_files=a_files, b_files=b_files)
    if ctx.errors:
        print("-----------------------------")
        print(f"File 1: {ctx.fa_path}")
        print(f"File 2: {ctx.fb_path}")
        print(f"File 1:       {a_files[0]}")
        print(f"File 2:       {b_files[0]}")
        print(f"Dependencies: {' '.join(a_files[1:])}")
        print("Errors:")
        for error in ctx.errors:
            print(f"  {error}")