dongyonglie5132
dongyonglie5132
2015-05-12 16:10
浏览 126
已采纳

如何比较两个源代码文件/ ast树?

I'm generating some source code using the templates package( is there a better method? )and part of the testing I need to check if the output matches the expected source code.

  • I tried a string comparison but it fails due the extra spaces / new lines generated by the templates package. I've also tried format.Source with not success. ( FAIL)
  • I tried to parse the ast of the both sources (see bellow) but the ast doesn't match either even if the code is basically same except the new lines / spaces. (FAIL)

    package main

    import (
        "fmt"
        "go/parser"
        "go/token"
        "reflect"
    )
    
    func main() {
        stub1 := `package main
         func myfunc(s string) error {
            return nil  
        }`
        stub2 := `package main
    
         func myfunc(s string) error {
    
            return nil
    
        }`
        fset := token.NewFileSet()
        r1, err := parser.ParseFile(fset, "", stub1, parser.AllErrors)
        if err != nil {
            panic(err)
        }
        fset = token.NewFileSet()
        r2, err := parser.ParseFile(fset, "", stub2, parser.AllErrors)
        if err != nil {
            panic(err)
        }
        if !reflect.DeepEqual(r1, r2) {
            fmt.Printf("e %v, r %s, ", r1, r2)
        }
    }
    

Playground

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

2条回答 默认 最新

  • dragon2025
    dragon2025 2015-05-15 13:35
    已采纳

    This was easier than I thought. All I had to do was to remove the empty new lines(after formatting). Below is the code.

        package main
    
        import (
            "fmt"
            "go/format"
            "strings"
        )
    
        func main() {
            a, err := fmtSource(stub1)
            if err != nil {
                panic(err)
            }
            b, err := fmtSource(stub2)
            if err != nil {
                panic(err)
            }
            if a != b {
                fmt.Printf("a %v, 
     b %v", a, b)
            }
        }
    
    func fmtSource(source string) (string, error) {
        if !strings.Contains(source, "package") {
            source = "package main
    " + source
        }
        b, err := format.Source([]byte(source))
        if err != nil {
            return "", err
        }
        // cleanLine replaces double space with one space
        cleanLine := func(s string)string{
            sa := strings.Fields(s)
            return strings.Join(sa, " ")
        }
        lines := strings.Split(string(b), "
    ")
        n := 0
        var startLn *int
        for _, line := range lines {
            if line != "" {
                line = cleanLine(line)
                lines[n] = line
                if startLn == nil {
                    x := n
                    startLn = &x
                }
                n++
            }
        }
        lines = lines[*startLn:n]
        // Add final "" entry to get trailing newline from Join.
        if n > 0 && lines[n-1] != "" {
            lines = append(lines, "")
        }
    
    
        // Make it pretty 
        b, err = format.Source([]byte(strings.Join(lines, "
    ")))
        if err != nil {
            return "", err
        }
        return string(b), nil
    }
    
    点赞 评论
  • doukong1391
    doukong1391 2015-05-12 17:35

    Well, one simple way to achieve this is to use the go/printer library, that gives you better control of output formatting, and is basically like running gofmt on the source, normalizing both trees:

    package main
    import (
        "fmt"
        "go/parser"
        "go/token"
        "go/printer"
        //"reflect"
        "bytes"
    )
    
    func main() {
        stub1 := `package main
         func myfunc(s string) error {
            return nil  
        }`
        stub2 := `package main
    
         func myfunc(s string) error {
    
            return nil
    
        }`
    
        fset1 := token.NewFileSet()
        r1, err := parser.ParseFile(fset1, "", stub1, parser.AllErrors)
        if err != nil {
            panic(err)
        }
        fset2 := token.NewFileSet()
        r2, err := parser.ParseFile(fset1, "", stub2, parser.AllErrors)
        if err != nil {
            panic(err)
        }
    
        // we create two output buffers for each source tree
        out1 := bytes.NewBuffer(nil)
        out2 := bytes.NewBuffer(nil)
    
        // we use the same printer config for both
        conf := &printer.Config{Mode: printer.TabIndent | printer.UseSpaces, Tabwidth: 8}
    
        // print to both outputs
        if err := conf.Fprint(out1, fset1, r1); err != nil {
            panic(err)
        }
        if err := conf.Fprint(out2, fset2, r2); err != nil {
            panic(err)
        }
    
    
        // they should be identical!
        if string(out1.Bytes()) != string(out2.Bytes()) {
            panic(string(out1.Bytes()) +"
    " + string(out2.Bytes()))
        } else {
            fmt.Println("A-OKAY!")
        }
    }
    

    Of course this code needs to be refactored to not look as stupid. Another approach is instead of using DeepEqual, create a tree comparison function yourself, that skips irrelevant nodes.

    点赞 评论

相关推荐