go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/starlark/docgen/symbols/loader_test.go (about)

     1  // Copyright 2019 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package symbols
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"strings"
    21  	"testing"
    22  
    23  	. "github.com/smartystreets/goconvey/convey"
    24  )
    25  
    26  var srcs = map[string]string{
    27  	"init.star": `
    28  load("another.star", _mod="exported")
    29  load("third.star", _third="exported")
    30  
    31  broken = pub1  # not defined yet
    32  
    33  pub1 = struct(
    34      sym = _mod.func,
    35      const = 123,
    36      broken1 = unknown,
    37      broken2 = _mod.unknown,
    38      broken3 = pub1,  # pub1 is assumed not defined yet
    39  )
    40  pub2 = _mod
    41  
    42  def pub3():
    43    pass
    44  
    45  pub4 = _third.deeper.deep
    46  
    47  pub5 = _mod.deeper.deep(
    48      arg1 = 123,
    49      arg2 = pub4,
    50  )
    51  
    52  pub6 = pub5
    53  `,
    54  
    55  	"another.star": `
    56  def _func():
    57    pass
    58  
    59  exported = struct(
    60      func = _func,
    61      deeper = struct(deep = _func),
    62  )
    63  
    64  `,
    65  
    66  	"third.star": `
    67  load("another.star", _mod="exported")
    68  exported = _mod
    69  `,
    70  }
    71  
    72  const expectedInitStar = `init.star = *ast.Namespace at init.star:2:1 {
    73    _mod = *ast.Namespace at another.star:5:1 {
    74      func = *ast.Function _func at another.star:2:1
    75      deeper = *ast.Namespace at another.star:7:5 {
    76        deep = *ast.Function _func at another.star:2:1
    77      }
    78    }
    79    _third = *ast.Namespace at another.star:5:1 {
    80      func = *ast.Function _func at another.star:2:1
    81      deeper = *ast.Namespace at another.star:7:5 {
    82        deep = *ast.Function _func at another.star:2:1
    83      }
    84    }
    85    broken = <broken>
    86    pub1 = *ast.Namespace at init.star:7:1 {
    87      sym = *ast.Function _func at another.star:2:1
    88      const = *ast.Var const at init.star:9:5
    89      broken1 = <broken>
    90      broken2 = <broken>
    91      broken3 = <broken>
    92    }
    93    pub2 = *ast.Namespace at another.star:5:1 {
    94      func = *ast.Function _func at another.star:2:1
    95      deeper = *ast.Namespace at another.star:7:5 {
    96        deep = *ast.Function _func at another.star:2:1
    97      }
    98    }
    99    pub3 = *ast.Function pub3 at init.star:16:1
   100    pub4 = *ast.Function _func at another.star:2:1
   101    pub5 = *ast.Invocation of *symbols.Term deep at init.star:21:1 {
   102      arg1 = *ast.Var arg1 at init.star:22:5
   103      arg2 = *ast.Function _func at another.star:2:1
   104    }
   105    pub6 = *ast.Invocation of *symbols.Term deep at init.star:21:1 {
   106      arg1 = *ast.Var arg1 at init.star:22:5
   107      arg2 = *ast.Function _func at another.star:2:1
   108    }
   109  }
   110  `
   111  
   112  const expectedThirdStar = `third.star = *ast.Namespace at third.star:2:1 {
   113    _mod = *ast.Namespace at another.star:5:1 {
   114      func = *ast.Function _func at another.star:2:1
   115      deeper = *ast.Namespace at another.star:7:5 {
   116        deep = *ast.Function _func at another.star:2:1
   117      }
   118    }
   119    exported = *ast.Namespace at another.star:5:1 {
   120      func = *ast.Function _func at another.star:2:1
   121      deeper = *ast.Namespace at another.star:7:5 {
   122        deep = *ast.Function _func at another.star:2:1
   123      }
   124    }
   125  }
   126  `
   127  
   128  func TestLoader(t *testing.T) {
   129  	t.Parallel()
   130  
   131  	Convey("Works", t, func() {
   132  		l := Loader{
   133  			Normalize: func(parent, module string) (string, error) { return module, nil },
   134  			Source:    source(srcs),
   135  		}
   136  
   137  		init, err := l.Load("init.star")
   138  		So(err, ShouldBeNil)
   139  		So(symbolToString(init), ShouldEqual, expectedInitStar)
   140  
   141  		third, err := l.Load("third.star")
   142  		So(err, ShouldBeNil)
   143  		So(symbolToString(third), ShouldEqual, expectedThirdStar)
   144  
   145  		// 'another.star' is not reparsed. Both init and third eventually refer to
   146  		// exact same AST nodes.
   147  		s1 := Lookup(init, "pub1", "sym")
   148  		s2 := Lookup(third, "exported", "func")
   149  		So(s1.Def().Name(), ShouldEqual, "_func") // correct node
   150  		So(s1.Def(), ShouldEqual, s2.Def())       // exact same pointers
   151  	})
   152  
   153  	Convey("Recursive deps", t, func() {
   154  		l := Loader{
   155  			Normalize: func(parent, module string) (string, error) { return module, nil },
   156  			Source: source(map[string]string{
   157  				"a.star": `load("b.star", "_")`,
   158  				"b.star": `load("a.star", "_")`,
   159  			})}
   160  		_, err := l.Load("a.star")
   161  		So(err.Error(), ShouldEqual, "in a.star: in b.star: in a.star: recursive dependency")
   162  	})
   163  }
   164  
   165  // source makes a source-provider function.
   166  func source(src map[string]string) func(string) (string, error) {
   167  	return func(module string) (string, error) {
   168  		body, ok := src[module]
   169  		if !ok {
   170  			return "", fmt.Errorf("no such module")
   171  		}
   172  		return body, nil
   173  	}
   174  }
   175  
   176  func symbolToString(s Symbol) string {
   177  	buf := strings.Builder{}
   178  	dumpTree(s, &buf, "")
   179  	return buf.String()
   180  }
   181  
   182  func dumpTree(s Symbol, w io.Writer, indent string) {
   183  	switch sym := s.(type) {
   184  	case *BrokenSymbol:
   185  		fmt.Fprintf(w, "%s%s = <broken>\n", indent, s.Name())
   186  	case *Term:
   187  		node := sym.Def()
   188  		pos, _ := node.Span()
   189  		fmt.Fprintf(w, "%s%s = %T %s at %s\n", indent, s.Name(), node, node.Name(), pos)
   190  	case *Invocation:
   191  		node := sym.Def()
   192  		pos, _ := node.Span()
   193  		fmt.Fprintf(w, "%s%s = %T of %T %s at %s {\n",
   194  			indent, s.Name(), node, sym.fn, sym.fn.Name(), pos)
   195  		for _, s := range sym.args {
   196  			dumpTree(s, w, indent+"  ")
   197  		}
   198  		fmt.Fprintf(w, "%s}\n", indent)
   199  	case *Struct:
   200  		node := sym.Def()
   201  		pos, _ := node.Span()
   202  		fmt.Fprintf(w, "%s%s = %T at %s {\n", indent, s.Name(), node, pos)
   203  		for _, s := range sym.symbols {
   204  			dumpTree(s, w, indent+"  ")
   205  		}
   206  		fmt.Fprintf(w, "%s}\n", indent)
   207  	}
   208  }