2015-05-12 14 views
22

Sto generando del codice sorgente utilizzando il pacchetto templates (esiste un metodo migliore?) E parte del test devo verificare se l'output corrisponde al codice sorgente previsto.Come posso confrontare due file di codice sorgente/ast alberi?

  • Ho provato un confronto tra stringhe ma non riesce a causa degli spazi aggiuntivi/nuove linee generate dal pacchetto di modelli. Ho anche provato format.Source con non successo. (FAIL)
  • Ho cercato di analizzare l'ast di entrambe le fonti (vedi sotto) ma l'ast non corrisponde neanche se il codice è sostanzialmente lo stesso eccetto le nuove linee/spazi. (FAIL)

    pacchetto principale

    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

+0

Vuoi confrontare alberi arbitrari, o semplicemente passare alberi che hai analizzato? –

+0

Basta andare codice sorgente/alberi, quindi il tag go – themihai

+0

https://www.diffchecker.com/ – Andrew

risposta

5

Questo è stato più facile di quanto pensassi. Tutto quello che dovevo fare era rimuovere le nuove righe vuote (dopo la formattazione). Di seguito è riportato il codice.

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, \n b %v", a, b) 
     } 
    } 

func fmtSource(source string) (string, error) { 
    if !strings.Contains(source, "package") { 
     source = "package main\n" + 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") 
    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, "\n"))) 
    if err != nil { 
     return "", err 
    } 
    return string(b), nil 
} 
9

Beh, in un modo semplice per raggiungere questo obiettivo è quello di utilizzare la libreria go/printer, che offre un migliore controllo della formattazione dell'output, ed è fondamentalmente come l'esecuzione di gofmt all'origine, normalizzando entrambi gli alberi:

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()) +"\n" + string(out2.Bytes())) 
    } else { 
     fmt.Println("A-OKAY!") 
    } 
} 

Ovviamente questo codice deve essere refactored per non sembrare così stupido. Un altro approccio è invece di utilizzare DeepEqual, creare una funzione di confronto ad albero da soli, che salta i nodi non pertinenti.

+0

La funzione di confronto potrebbe non essere banale, quindi sto cercando di evitarlo. la stampante sembra fallire su strutture più "complesse" http://play.golang.org/p/I9cAVEYLAm – themihai

+0

@mihai forse attraversa l'albero e lo filtra, quindi usa DeepEqual? –

+0

Qualche idea su quanto esattamente posso filtrare i nodi irrilevanti? Ho provato a rimuovere i nodi nulli assumendo che rappresentino le nuove linee/spazi ma non sembra essere il caso. play.golang.org/p/JVVpKIzela – themihai