Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0cead769 authored by Makoto Onuki's avatar Makoto Onuki
Browse files

[hoststubgen] Reusable TextFileFilterPolicyParser

- Make TextFileFilterPolicyParser reusable.
- Tweak the "dump" test to address b/388562869

Flag: EXEMPT host test change only
Bug: 388607679
Test: $ANDROID_BUILD_TOP/frameworks/base/ravenwood/scripts/run-ravenwood-tests.sh -s

Change-Id: Ib5ab0e4c5070f46704fdc14400fec0f06c430990
parent ca007fa3
Loading
Loading
Loading
Loading
+1 −1
Original line number Original line Diff line number Diff line
@@ -89,7 +89,7 @@ filter_output() {
    # - Some other transient lines
    # - Some other transient lines
    # - Sometimes the javap shows mysterious warnings, so remove them too.
    # - Sometimes the javap shows mysterious warnings, so remove them too.
    #
    #
    # `/PATTERN-1/,/PATTERN-1/{//!d}` is a trick to delete lines between two patterns, without
    # `/PATTERN-1/,/PATTERN-2/{//!d}` is a trick to delete lines between two patterns, without
    # the start and the end lines.
    # the start and the end lines.
    sed -e 's/#[0-9][0-9]*/#x/g' \
    sed -e 's/#[0-9][0-9]*/#x/g' \
        -e 's/^\( *\)[0-9][0-9]*:/\1x:/' \
        -e 's/^\( *\)[0-9][0-9]*:/\1x:/' \
+4 −4
Original line number Original line Diff line number Diff line
@@ -27,7 +27,7 @@ import com.android.hoststubgen.filters.ImplicitOutputFilter
import com.android.hoststubgen.filters.KeepNativeFilter
import com.android.hoststubgen.filters.KeepNativeFilter
import com.android.hoststubgen.filters.OutputFilter
import com.android.hoststubgen.filters.OutputFilter
import com.android.hoststubgen.filters.SanitizationFilter
import com.android.hoststubgen.filters.SanitizationFilter
import com.android.hoststubgen.filters.TextFileFilterPolicyParser
import com.android.hoststubgen.filters.TextFileFilterPolicyBuilder
import com.android.hoststubgen.filters.printAsTextPolicy
import com.android.hoststubgen.filters.printAsTextPolicy
import com.android.hoststubgen.utils.ClassFilter
import com.android.hoststubgen.utils.ClassFilter
import com.android.hoststubgen.visitors.BaseAdapter
import com.android.hoststubgen.visitors.BaseAdapter
@@ -179,9 +179,9 @@ class HostStubGen(val options: HostStubGenOptions) {
        // Next, "text based" filter, which allows to override polices without touching
        // Next, "text based" filter, which allows to override polices without touching
        // the target code.
        // the target code.
        if (options.policyOverrideFiles.isNotEmpty()) {
        if (options.policyOverrideFiles.isNotEmpty()) {
            val parser = TextFileFilterPolicyParser(allClasses, filter)
            val builder = TextFileFilterPolicyBuilder(allClasses, filter)
            options.policyOverrideFiles.forEach(parser::parse)
            options.policyOverrideFiles.forEach(builder::parse)
            filter = parser.createOutputFilter()
            filter = builder.createOutputFilter()
        }
        }


        // Apply the implicit filter.
        // Apply the implicit filter.
+305 −68
Original line number Original line Diff line number Diff line
@@ -23,10 +23,12 @@ import com.android.hoststubgen.asm.toJvmClassName
import com.android.hoststubgen.log
import com.android.hoststubgen.log
import com.android.hoststubgen.normalizeTextLine
import com.android.hoststubgen.normalizeTextLine
import com.android.hoststubgen.whitespaceRegex
import com.android.hoststubgen.whitespaceRegex
import java.io.File
import org.objectweb.asm.tree.ClassNode
import java.io.BufferedReader
import java.io.FileReader
import java.io.PrintWriter
import java.io.PrintWriter
import java.io.Reader
import java.util.regex.Pattern
import java.util.regex.Pattern
import org.objectweb.asm.tree.ClassNode


/**
/**
 * Print a class node as a "keep" policy.
 * Print a class node as a "keep" policy.
@@ -48,7 +50,7 @@ fun printAsTextPolicy(pw: PrintWriter, cn: ClassNode) {


private const val FILTER_REASON = "file-override"
private const val FILTER_REASON = "file-override"


private enum class SpecialClass {
enum class SpecialClass {
    NotSpecial,
    NotSpecial,
    Aidl,
    Aidl,
    FeatureFlags,
    FeatureFlags,
@@ -56,10 +58,58 @@ private enum class SpecialClass {
    RFile,
    RFile,
}
}


class TextFileFilterPolicyParser(
/**
 * This receives [TextFileFilterPolicyBuilder] parsing result.
 */
interface PolicyFileProcessor {
    /** "package" directive. */
    fun onPackage(name: String, policy: FilterPolicyWithReason)

    /** "rename" directive. */
    fun onRename(pattern: Pattern, prefix: String)

    /** "class" directive. */
    fun onSimpleClassStart(className: String)
    fun onSimpleClassPolicy(className: String, policy: FilterPolicyWithReason)
    fun onSimpleClassEnd(className: String)

    fun onSubClassPolicy(superClassName: String, policy: FilterPolicyWithReason)
    fun onRedirectionClass(fromClassName: String, toClassName: String)
    fun onClassLoadHook(className: String, callback: String)
    fun onSpecialClassPolicy(type: SpecialClass, policy: FilterPolicyWithReason)

    /** "field" directive. */
    fun onField(className: String, fieldName: String, policy: FilterPolicyWithReason)

    /** "method" directive. */
    fun onSimpleMethodPolicy(
        className: String,
        methodName: String,
        methodDesc: String,
        policy: FilterPolicyWithReason,
    )
    fun onMethodInClassReplace(
        className: String,
        methodName: String,
        methodDesc: String,
        targetName: String,
        policy: FilterPolicyWithReason,
    )
    fun onMethodOutClassReplace(
        className: String,
        methodName: String,
        methodDesc: String,
        replaceSpec: TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec,
        policy: FilterPolicyWithReason,
    )
}

class TextFileFilterPolicyBuilder(
    private val classes: ClassNodes,
    private val classes: ClassNodes,
    fallback: OutputFilter
    fallback: OutputFilter
) {
) {
    private val parser = TextFileFilterPolicyParser()

    private val subclassFilter = SubclassFilter(classes, fallback)
    private val subclassFilter = SubclassFilter(classes, fallback)
    private val packageFilter = PackageFilter(subclassFilter)
    private val packageFilter = PackageFilter(subclassFilter)
    private val imf = InMemoryOutputFilter(classes, packageFilter)
    private val imf = InMemoryOutputFilter(classes, packageFilter)
@@ -71,30 +121,19 @@ class TextFileFilterPolicyParser(
    private val methodReplaceSpec =
    private val methodReplaceSpec =
        mutableListOf<TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec>()
        mutableListOf<TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec>()


    private lateinit var currentClassName: String

    /**
    /**
     * Read a given "policy" file and return as an [OutputFilter]
     * Parse a given policy file. This method can be called multiple times to read from
     * multiple files. To get the resulting filter, use [createOutputFilter]
     */
     */
    fun parse(file: String) {
    fun parse(file: String) {
        log.i("Loading offloaded annotations from $file ...")
        // We may parse multiple files, but we reuse the same parser, because the parser
        log.withIndent {
        // will make sure there'll be no dupplicating "special class" policies.
            var lineNo = 0
        parser.parse(FileReader(file), file, Processor())
            try {
                File(file).forEachLine {
                    lineNo++
                    val line = normalizeTextLine(it)
                    if (line.isEmpty()) {
                        return@forEachLine // skip empty lines.
                    }
                    parseLine(line)
                }
            } catch (e: ParseException) {
                throw e.withSourceInfo(file, lineNo)
            }
        }
    }
    }


    /**
     * Generate the resulting [OutputFilter].
     */
    fun createOutputFilter(): OutputFilter {
    fun createOutputFilter(): OutputFilter {
        var ret: OutputFilter = imf
        var ret: OutputFilter = imf
        if (typeRenameSpec.isNotEmpty()) {
        if (typeRenameSpec.isNotEmpty()) {
@@ -112,14 +151,200 @@ class TextFileFilterPolicyParser(
        return ret
        return ret
    }
    }


    private inner class Processor : PolicyFileProcessor {
        override fun onPackage(name: String, policy: FilterPolicyWithReason) {
            packageFilter.addPolicy(name, policy)
        }

        override fun onRename(pattern: Pattern, prefix: String) {
            typeRenameSpec += TextFilePolicyRemapperFilter.TypeRenameSpec(
                pattern, prefix
            )
        }

        override fun onSimpleClassStart(className: String) {
        }

        override fun onSimpleClassEnd(className: String) {
        }

        override fun onSimpleClassPolicy(className: String, policy: FilterPolicyWithReason) {
            imf.setPolicyForClass(className, policy)
        }

        override fun onSubClassPolicy(
            superClassName: String,
            policy: FilterPolicyWithReason,
            ) {
            log.i("class extends $superClassName")
            subclassFilter.addPolicy( superClassName, policy)
        }

        override fun onRedirectionClass(fromClassName: String, toClassName: String) {
            imf.setRedirectionClass(fromClassName, toClassName)
        }

        override fun onClassLoadHook(className: String, callback: String) {
            imf.setClassLoadHook(className, callback)
        }

        override fun onSpecialClassPolicy(
            type: SpecialClass,
            policy: FilterPolicyWithReason,
        ) {
            log.i("class special $type $policy")
            when (type) {
                SpecialClass.NotSpecial -> {} // Shouldn't happen

                SpecialClass.Aidl -> {
                    aidlPolicy = policy
                }

                SpecialClass.FeatureFlags -> {
                    featureFlagsPolicy = policy
                }

                SpecialClass.Sysprops -> {
                    syspropsPolicy = policy
                }

                SpecialClass.RFile -> {
                    rFilePolicy = policy
                }
            }
        }

        override fun onField(className: String, fieldName: String, policy: FilterPolicyWithReason) {
            imf.setPolicyForField(className, fieldName, policy)
        }

        override fun onSimpleMethodPolicy(
            className: String,
            methodName: String,
            methodDesc: String,
            policy: FilterPolicyWithReason,
        ) {
            imf.setPolicyForMethod(className, methodName, methodDesc, policy)
        }

        override fun onMethodInClassReplace(
            className: String,
            methodName: String,
            methodDesc: String,
            targetName: String,
            policy: FilterPolicyWithReason,
        ) {
            imf.setPolicyForMethod(className, methodName, methodDesc, policy)

            // Make sure to keep the target method.
            imf.setPolicyForMethod(
                className,
                targetName,
                methodDesc,
                FilterPolicy.Keep.withReason(FILTER_REASON)
            )
            // Set up the rename.
            imf.setRenameTo(className, targetName, methodDesc, methodName)
        }

        override fun onMethodOutClassReplace(
            className: String,
            methodName: String,
            methodDesc: String,
            replaceSpec: TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec,
            policy: FilterPolicyWithReason,
        ) {
            imf.setPolicyForMethod(className, methodName, methodDesc, policy)
            methodReplaceSpec.add(replaceSpec)
        }
    }
}

/**
 * Parses a filer policy text file.
 */
class TextFileFilterPolicyParser {
    private lateinit var processor: PolicyFileProcessor
    private var currentClassName: String? = null

    private var aidlPolicy: FilterPolicyWithReason? = null
    private var featureFlagsPolicy: FilterPolicyWithReason? = null
    private var syspropsPolicy: FilterPolicyWithReason? = null
    private var rFilePolicy: FilterPolicyWithReason? = null

    /** Name of the file that's currently being processed.  */
    var filename: String? = null
        private set

    /** 1-based line number in the current file */
    var lineNumber = -1
        private set

    /**
     * Parse a given "policy" file.
     */
    fun parse(reader: Reader, inputName: String, processor: PolicyFileProcessor) {
        filename = inputName

        log.i("Parsing text policy file $inputName ...")
        this.processor = processor
        BufferedReader(reader).use { rd ->
            lineNumber = 0
            try {
                while (true) {
                    var line = rd.readLine()
                    if (line == null) {
                        break
                    }
                    lineNumber++
                    line = normalizeTextLine(line) // Remove comment and trim.
                    if (line.isEmpty()) {
                        continue
                    }
                    parseLine(line)
                }
                finishLastClass()
            } catch (e: ParseException) {
                throw e.withSourceInfo(inputName, lineNumber)
            }
        }
    }

    private fun finishLastClass() {
        currentClassName?.let { className ->
            processor.onSimpleClassEnd(className)
            currentClassName = null
        }
    }

    private fun ensureInClass(directive: String): String {
        return currentClassName ?:
            throw ParseException("Directive '$directive' must follow a 'class' directive")
    }

    private fun parseLine(line: String) {
    private fun parseLine(line: String) {
        val fields = line.split(whitespaceRegex).toTypedArray()
        val fields = line.split(whitespaceRegex).toTypedArray()
        when (fields[0].lowercase()) {
        when (fields[0].lowercase()) {
            "p", "package" -> parsePackage(fields)
            "p", "package" -> {
            "c", "class" -> parseClass(fields)
                finishLastClass()
            "f", "field" -> parseField(fields)
                parsePackage(fields)
            "m", "method" -> parseMethod(fields)
            }
            "r", "rename" -> parseRename(fields)
            "c", "class" -> {
                finishLastClass()
                parseClass(fields)
            }
            "f", "field" -> {
                ensureInClass("field")
                parseField(fields)
            }
            "m", "method" -> {
                ensureInClass("method")
                parseMethod(fields)
            }
            "r", "rename" -> {
                finishLastClass()
                parseRename(fields)
            }
            else -> throw ParseException("Unknown directive \"${fields[0]}\"")
            else -> throw ParseException("Unknown directive \"${fields[0]}\"")
        }
        }
    }
    }
@@ -184,20 +409,20 @@ class TextFileFilterPolicyParser(
        if (!policy.isUsableWithClasses) {
        if (!policy.isUsableWithClasses) {
            throw ParseException("Package can't have policy '$policy'")
            throw ParseException("Package can't have policy '$policy'")
        }
        }
        packageFilter.addPolicy(name, policy.withReason(FILTER_REASON))
        processor.onPackage(name, policy.withReason(FILTER_REASON))
    }
    }


    private fun parseClass(fields: Array<String>) {
    private fun parseClass(fields: Array<String>) {
        if (fields.size < 3) {
        if (fields.size < 3) {
            throw ParseException("Class ('c') expects 2 fields.")
            throw ParseException("Class ('c') expects 2 fields.")
        }
        }
        currentClassName = fields[1]
        val className = fields[1]


        // superClass is set when the class name starts with a "*".
        // superClass is set when the class name starts with a "*".
        val superClass = resolveExtendingClass(currentClassName)
        val superClass = resolveExtendingClass(className)


        // :aidl, etc?
        // :aidl, etc?
        val classType = resolveSpecialClass(currentClassName)
        val classType = resolveSpecialClass(className)


        if (fields[2].startsWith("!")) {
        if (fields[2].startsWith("!")) {
            if (classType != SpecialClass.NotSpecial) {
            if (classType != SpecialClass.NotSpecial) {
@@ -208,7 +433,8 @@ class TextFileFilterPolicyParser(
            }
            }
            // It's a redirection class.
            // It's a redirection class.
            val toClass = fields[2].substring(1)
            val toClass = fields[2].substring(1)
            imf.setRedirectionClass(currentClassName, toClass)

            processor.onRedirectionClass(className, toClass)
        } else if (fields[2].startsWith("~")) {
        } else if (fields[2].startsWith("~")) {
            if (classType != SpecialClass.NotSpecial) {
            if (classType != SpecialClass.NotSpecial) {
                // We could support it, but not needed at least for now.
                // We could support it, but not needed at least for now.
@@ -218,7 +444,8 @@ class TextFileFilterPolicyParser(
            }
            }
            // It's a class-load hook
            // It's a class-load hook
            val callback = fields[2].substring(1)
            val callback = fields[2].substring(1)
            imf.setClassLoadHook(currentClassName, callback)

            processor.onClassLoadHook(className, callback)
        } else {
        } else {
            val policy = parsePolicy(fields[2])
            val policy = parsePolicy(fields[2])
            if (!policy.isUsableWithClasses) {
            if (!policy.isUsableWithClasses) {
@@ -229,26 +456,27 @@ class TextFileFilterPolicyParser(
                SpecialClass.NotSpecial -> {
                SpecialClass.NotSpecial -> {
                    // TODO: Duplicate check, etc
                    // TODO: Duplicate check, etc
                    if (superClass == null) {
                    if (superClass == null) {
                        imf.setPolicyForClass(
                        currentClassName = className
                            currentClassName, policy.withReason(FILTER_REASON)
                        processor.onSimpleClassStart(className)
                        )
                        processor.onSimpleClassPolicy(className, policy.withReason(FILTER_REASON))
                    } else {
                    } else {
                        subclassFilter.addPolicy(
                        processor.onSubClassPolicy(
                            superClass,
                            superClass,
                            policy.withReason("extends $superClass")
                            policy.withReason("extends $superClass"),
                        )
                        )
                    }
                    }
                }
                }

                SpecialClass.Aidl -> {
                SpecialClass.Aidl -> {
                    if (aidlPolicy != null) {
                    if (aidlPolicy != null) {
                        throw ParseException(
                        throw ParseException(
                            "Policy for AIDL classes already defined"
                            "Policy for AIDL classes already defined"
                        )
                        )
                    }
                    }
                    aidlPolicy = policy.withReason(
                    val p = policy.withReason(
                        "$FILTER_REASON (special-class AIDL)"
                        "$FILTER_REASON (special-class AIDL)"
                    )
                    )
                    processor.onSpecialClassPolicy(classType, p)
                    aidlPolicy = p
                }
                }


                SpecialClass.FeatureFlags -> {
                SpecialClass.FeatureFlags -> {
@@ -257,9 +485,11 @@ class TextFileFilterPolicyParser(
                            "Policy for feature flags already defined"
                            "Policy for feature flags already defined"
                        )
                        )
                    }
                    }
                    featureFlagsPolicy = policy.withReason(
                    val p = policy.withReason(
                        "$FILTER_REASON (special-class feature flags)"
                        "$FILTER_REASON (special-class feature flags)"
                    )
                    )
                    processor.onSpecialClassPolicy(classType, p)
                    featureFlagsPolicy = p
                }
                }


                SpecialClass.Sysprops -> {
                SpecialClass.Sysprops -> {
@@ -268,9 +498,11 @@ class TextFileFilterPolicyParser(
                            "Policy for sysprops already defined"
                            "Policy for sysprops already defined"
                        )
                        )
                    }
                    }
                    syspropsPolicy = policy.withReason(
                    val p = policy.withReason(
                        "$FILTER_REASON (special-class sysprops)"
                        "$FILTER_REASON (special-class sysprops)"
                    )
                    )
                    processor.onSpecialClassPolicy(classType, p)
                    syspropsPolicy = p
                }
                }


                SpecialClass.RFile -> {
                SpecialClass.RFile -> {
@@ -279,9 +511,11 @@ class TextFileFilterPolicyParser(
                            "Policy for R file already defined"
                            "Policy for R file already defined"
                        )
                        )
                    }
                    }
                    rFilePolicy = policy.withReason(
                    val p = policy.withReason(
                        "$FILTER_REASON (special-class R file)"
                        "$FILTER_REASON (special-class R file)"
                    )
                    )
                    processor.onSpecialClassPolicy(classType, p)
                    rFilePolicy = p
                }
                }
            }
            }
        }
        }
@@ -296,17 +530,16 @@ class TextFileFilterPolicyParser(
        if (!policy.isUsableWithFields) {
        if (!policy.isUsableWithFields) {
            throw ParseException("Field can't have policy '$policy'")
            throw ParseException("Field can't have policy '$policy'")
        }
        }
        require(this::currentClassName.isInitialized)


        // TODO: Duplicate check, etc
        // TODO: Duplicate check, etc
        imf.setPolicyForField(currentClassName, name, policy.withReason(FILTER_REASON))
        processor.onField(currentClassName!!, name, policy.withReason(FILTER_REASON))
    }
    }


    private fun parseMethod(fields: Array<String>) {
    private fun parseMethod(fields: Array<String>) {
        if (fields.size < 3 || fields.size > 4) {
        if (fields.size < 3 || fields.size > 4) {
            throw ParseException("Method ('m') expects 3 or 4 fields.")
            throw ParseException("Method ('m') expects 3 or 4 fields.")
        }
        }
        val name = fields[1]
        val methodName = fields[1]
        val signature: String
        val signature: String
        val policyStr: String
        val policyStr: String
        if (fields.size <= 3) {
        if (fields.size <= 3) {
@@ -323,44 +556,48 @@ class TextFileFilterPolicyParser(
            throw ParseException("Method can't have policy '$policy'")
            throw ParseException("Method can't have policy '$policy'")
        }
        }


        require(this::currentClassName.isInitialized)
        val className = currentClassName!!


        imf.setPolicyForMethod(
        val policyWithReason = policy.withReason(FILTER_REASON)
            currentClassName, name, signature,
        if (policy != FilterPolicy.Substitute) {
            policy.withReason(FILTER_REASON)
            processor.onSimpleMethodPolicy(className, methodName, signature, policyWithReason)
        )
        } else {
        if (policy == FilterPolicy.Substitute) {
            val targetName = policyStr.substring(1)
            val fromName = policyStr.substring(1)


            if (fromName == name) {
            if (targetName == methodName) {
                throw ParseException(
                throw ParseException(
                    "Substitution must have a different name"
                    "Substitution must have a different name"
                )
                )
            }
            }


            // Set the policy for the "from" method.
            val classAndMethod = splitWithLastPeriod(targetName)
            imf.setPolicyForMethod(
                currentClassName, fromName, signature,
                FilterPolicy.Keep.withReason(FILTER_REASON)
            )

            val classAndMethod = splitWithLastPeriod(fromName)
            if (classAndMethod != null) {
            if (classAndMethod != null) {
                // If the substitution target contains a ".", then
                // If the substitution target contains a ".", then
                // it's a method call redirect.
                // it's a method call redirect.
                methodReplaceSpec.add(
                val spec = TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec(
                    TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec(
                        currentClassName!!.toJvmClassName(),
                        currentClassName.toJvmClassName(),
                        methodName,
                        name,
                        signature,
                        signature,
                        classAndMethod.first.toJvmClassName(),
                        classAndMethod.first.toJvmClassName(),
                        classAndMethod.second,
                        classAndMethod.second,
                    )
                    )
                processor.onMethodOutClassReplace(
                    className,
                    methodName,
                    signature,
                    spec,
                    policyWithReason,
                )
                )
            } else {
            } else {
                // It's an in-class replace.
                // It's an in-class replace.
                // ("@RavenwoodReplace" equivalent)
                // ("@RavenwoodReplace" equivalent)
                imf.setRenameTo(currentClassName, fromName, signature, name)
                processor.onMethodInClassReplace(
                    className,
                    methodName,
                    signature,
                    targetName,
                    policyWithReason,
                )
            }
            }
        }
        }
    }
    }
@@ -378,7 +615,7 @@ class TextFileFilterPolicyParser(
        // applied. (Which is needed for services.jar)
        // applied. (Which is needed for services.jar)
        val prefix = fields[2].trimStart('/')
        val prefix = fields[2].trimStart('/')


        typeRenameSpec += TextFilePolicyRemapperFilter.TypeRenameSpec(
        processor.onRename(
            pattern, prefix
            pattern, prefix
        )
        )
    }
    }
+4 −3
Original line number Original line Diff line number Diff line
@@ -34,10 +34,11 @@ source "${0%/*}"/../common.sh


SCRIPT_NAME="${0##*/}"
SCRIPT_NAME="${0##*/}"


GOLDEN_DIR=golden-output
GOLDEN_DIR=${GOLDEN_DIR:-golden-output}
mkdir -p $GOLDEN_DIR
mkdir -p $GOLDEN_DIR


DIFF_CMD=${DIFF:-diff -u --ignore-blank-lines --ignore-space-change}
# TODO(b/388562869) We shouldn't need `--ignore-matching-lines`, but the golden files may not have the "Constant pool:" lines.
DIFF_CMD=${DIFF_CMD:-diff -u --ignore-blank-lines --ignore-space-change --ignore-matching-lines='^\(Constant.pool:\|{\)$'}


update=0
update=0
three_way=0
three_way=0
@@ -62,7 +63,7 @@ done
shift $(($OPTIND - 1))
shift $(($OPTIND - 1))


# Build the dump files, which are the input of this test.
# Build the dump files, which are the input of this test.
run m  dump-jar tiny-framework-dump-test
run ${BUILD_CMD:=m} dump-jar tiny-framework-dump-test




# Get the path to the generate text files. (not the golden files.)
# Get the path to the generate text files. (not the golden files.)
+4 −1
Original line number Original line Diff line number Diff line
@@ -28,8 +28,11 @@ GOLDEN_DIRS = [


# Run diff.
# Run diff.
def run_diff(file1, file2):
def run_diff(file1, file2):
    # TODO(b/388562869) We shouldn't need `--ignore-matching-lines`, but the golden files may not have the "Constant pool:" lines.
    command = ['diff', '-u', '--ignore-blank-lines',
    command = ['diff', '-u', '--ignore-blank-lines',
               '--ignore-space-change', file1, file2]
               '--ignore-space-change',
               '--ignore-matching-lines=^\(Constant.pool:\|{\)$',
               file1, file2]
    print(' '.join(command))
    print(' '.join(command))
    result = subprocess.run(command, stderr=sys.stdout)
    result = subprocess.run(command, stderr=sys.stdout)