go.fuchsia.dev/infra@v0.0.0-20240507153436-9b593402251b/cmd/autogardener/changed_files.go (about)

     1  // Copyright 2022 The Fuchsia Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style license that can be
     3  // found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"strings"
     9  )
    10  
    11  // scoreChangedFileProximity computes a 0-100 score of the proximity of a Gerrit
    12  // change's affected files to the given test GN label.
    13  func scoreChangedFileProximity(changedFiles []string, gnLabel string) int {
    14  	// If there's no GN label and we can't do this analysis, that significantly
    15  	// lowers the confidence.
    16  	if gnLabel == "" {
    17  		return 0
    18  	}
    19  
    20  	strippedLabel := strings.Trim(strings.Split(gnLabel, ":")[0], "/")
    21  	labelParts := strings.Split(strippedLabel, "/")
    22  
    23  	var proximityScores []float64
    24  
    25  	for _, file := range changedFiles {
    26  		pl := sharedPrefixLength(labelParts, strings.Split(file, "/"))
    27  
    28  		// Proximity is determined by the ratio of the shared prefix length to
    29  		// the length of the GN label directory.
    30  		//
    31  		// If a test is declared in directory `src/foo/bar`, any files within
    32  		// `src/foo/bar` or any subdirectory thereof will have a proximity ratio
    33  		// of 1.
    34  		proximityScore := 100 * float64(pl) / float64(len(labelParts))
    35  
    36  		proximityScores = append(proximityScores, proximityScore)
    37  
    38  		// Add extra weighting for higher proximity scores. If a change touches
    39  		// a couple files close to the test, along with a large number of
    40  		// unrelated files, it should still be scored higher than a change that
    41  		// touches only files that are moderately close to the test.
    42  		for range int(proximityScore/25) + 1 {
    43  			proximityScores = append(proximityScores, proximityScore)
    44  		}
    45  	}
    46  
    47  	return int(average(proximityScores))
    48  }
    49  
    50  // sharedPrefixLength computes the length of the longest shared prefix of the
    51  // two slices, i.e. the maximum number N such that p1[:N] == p2[:N].
    52  func sharedPrefixLength(p1, p2 []string) int {
    53  	var i int
    54  	for ; i < len(p1) && i < len(p2); i++ {
    55  		if p1[i] != p2[i] {
    56  			break
    57  		}
    58  	}
    59  	return i
    60  }