<commit_before>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package oracle_test

// This file defines a test framework for oracle queries.
//
// The files beneath testdata/src/main contain Go programs containing
// query annotations of the form:
//
//   @verb id "select"
//
// where verb is the query mode (e.g. "callers"), id is a unique name
// for this query, and "select" is a regular expression matching the
// substring of the current line that is the query's input selection.
//
// The expected output for each query is provided in the accompanying
// .golden file.
//
// (Location information is not included because it's too fragile to
// display as text.  TODO(adonovan): think about how we can test its
// correctness, since it is critical information.)
//
// Run this test with:
// 	% go test code.google.com/p/go.tools/oracle -update
// to update the golden files.

// TODO(adonovan): improve coverage:
// - output of @callgraph is nondeterministic.
// - as are lists of labels.

import (
	"bytes"
	"flag"
	"fmt"
	"go/build"
	"go/parser"
	"go/token"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
	"testing"

	"code.google.com/p/go.tools/oracle"
)

var updateFlag = flag.Bool("update", false, "Update the golden files.")

type query struct {
	id         string         // unique id
	verb       string         // query mode, e.g. "callees"
	posn       token.Position // position of of query
	filename   string
	start, end int // selection of file to pass to oracle
}

func parseRegexp(text string) (*regexp.Regexp, error) {
	pattern, err := strconv.Unquote(text)
	if err != nil {
		return nil, fmt.Errorf("can't unquote %s", text)
	}
	return regexp.Compile(pattern)
}

// parseQueries parses and returns the queries in the named file.
func parseQueries(t *testing.T, filename string) []*query {
	filedata, err := ioutil.ReadFile(filename)
	if err != nil {
		t.Fatal(err)
	}

	// Parse the file once to discover the test queries.
	var fset token.FileSet
	f, err := parser.ParseFile(&fset, filename, filedata,
		parser.DeclarationErrors|parser.ParseComments)
	if err != nil {
		t.Fatal(err)
	}

	lines := bytes.Split(filedata, []byte("\n"))

	var queries []*query
	queriesById := make(map[string]*query)

	// Find all annotations of these forms:
	expectRe := regexp.MustCompile(`@([a-z]+)\s+(\S+)\s+(\".*)$`) // @verb id "regexp"
	for _, c := range f.Comments {
		text := strings.TrimSpace(c.Text())
		if text == "" || text[0] != '@' {
			continue
		}
		posn := fset.Position(c.Pos())

		// @verb id "regexp"
		match := expectRe.FindStringSubmatch(text)
		if match == nil {
			t.Errorf("%s: ill-formed query: %s", posn, text)
			continue
		}

		id := match[2]
		if prev, ok := queriesById[id]; ok {
			t.Errorf("%s: duplicate id %s", posn, id)
			t.Errorf("%s: previously used here", prev.posn)
			continue
		}

		selectRe, err := parseRegexp(match[3])
		if err != nil {
			t.Errorf("%s: %s", posn, err)
			continue
		}

		// Find text of the current line, sans query.
		// (Queries must be // not /**/ comments.)
		line := lines[posn.Line-1][:posn.Column-1]

		// Apply regexp to current line to find input selection.
		loc := selectRe.FindIndex(line)
		if loc == nil {
			t.Errorf("%s: selection pattern %s doesn't match line %q",
				posn, match[3], string(line))
			continue
		}

		// Assumes ASCII. TODO(adonovan): test on UTF-8.
		linestart := posn.Offset - (posn.Column - 1)

		// Compute the file offsets
		q := &query{
			id:       id,
			verb:     match[1],
			posn:     posn,
			filename: filename,
			start:    linestart + loc[0],
			end:      linestart + loc[1],
		}
		queries = append(queries, q)
		queriesById[id] = q
	}

	// Return the slice, not map, for deterministic iteration.
	return queries
}

// stripLocation removes a "file:line: " prefix.
func stripLocation(line string) string {
	if i := strings.Index(line, ": "); i >= 0 {
		line = line[i+2:]
	}
	return line
}

// doQuery poses query q to the oracle and writes its response and
// error (if any) to out.
func doQuery(out io.Writer, q *query) {
	fmt.Fprintf(out, "-------- @%s %s --------\n", q.verb, q.id)

	capture := new(bytes.Buffer) // capture standard output
	var buildContext = build.Default
	buildContext.GOPATH = "testdata"
	err := oracle.Main([]string{q.filename},
		q.verb,
		fmt.Sprintf("%s %d-%d", q.filename, q.start, q.end),
		/*PTA-log=*/ nil, capture, &buildContext)

	for _, line := range strings.Split(capture.String(), "\n") {
		fmt.Fprintf(out, "%s\n", stripLocation(line))
	}

	if err != nil {
		fmt.Fprintf(out, "Error: %s\n", stripLocation(err.Error()))
	}
}

func TestOracle(t *testing.T) {
	for _, filename := range []string{
		"testdata/src/main/calls.go",
		"testdata/src/main/describe.go",
		"testdata/src/main/freevars.go",
		"testdata/src/main/implements.go",
		"testdata/src/main/imports.go",
		"testdata/src/main/peers.go",
	} {
		queries := parseQueries(t, filename)
		golden := filename + "lden"
		got := filename + "t"
		gotfh, err := os.Create(got)
		if err != nil {<commit_msg>go.tools/oracle: disable TestOracle test on windows (fixes windows build)<commit_after>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package oracle_test

// This file defines a test framework for oracle queries.
//
// The files beneath testdata/src/main contain Go programs containing
// query annotations of the form:
//
//   @verb id "select"
//
// where verb is the query mode (e.g. "callers"), id is a unique name
// for this query, and "select" is a regular expression matching the
// substring of the current line that is the query's input selection.
//
// The expected output for each query is provided in the accompanying
// .golden file.
//
// (Location information is not included because it's too fragile to
// display as text.  TODO(adonovan): think about how we can test its
// correctness, since it is critical information.)
//
// Run this test with:
// 	% go test code.google.com/p/go.tools/oracle -update
// to update the golden files.

// TODO(adonovan): improve coverage:
// - output of @callgraph is nondeterministic.
// - as are lists of labels.

import (
	"bytes"
	"flag"
	"fmt"
	"go/build"
	"go/parser"
	"go/token"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"testing"

	"code.google.com/p/go.tools/oracle"
)

var updateFlag = flag.Bool("update", false, "Update the golden files.")

type query struct {
	id         string         // unique id
	verb       string         // query mode, e.g. "callees"
	posn       token.Position // position of of query
	filename   string
	start, end int // selection of file to pass to oracle
}

func parseRegexp(text string) (*regexp.Regexp, error) {
	pattern, err := strconv.Unquote(text)
	if err != nil {
		return nil, fmt.Errorf("can't unquote %s", text)
	}
	return regexp.Compile(pattern)
}

// parseQueries parses and returns the queries in the named file.
func parseQueries(t *testing.T, filename string) []*query {
	filedata, err := ioutil.ReadFile(filename)
	if err != nil {
		t.Fatal(err)
	}

	// Parse the file once to discover the test queries.
	var fset token.FileSet
	f, err := parser.ParseFile(&fset, filename, filedata,
		parser.DeclarationErrors|parser.ParseComments)
	if err != nil {
		t.Fatal(err)
	}

	lines := bytes.Split(filedata, []byte("\n"))

	var queries []*query
	queriesById := make(map[string]*query)

	// Find all annotations of these forms:
	expectRe := regexp.MustCompile(`@([a-z]+)\s+(\S+)\s+(\".*)$`) // @verb id "regexp"
	for _, c := range f.Comments {
		text := strings.TrimSpace(c.Text())
		if text == "" || text[0] != '@' {
			continue
		}
		posn := fset.Position(c.Pos())

		// @verb id "regexp"
		match := expectRe.FindStringSubmatch(text)
		if match == nil {
			t.Errorf("%s: ill-formed query: %s", posn, text)
			continue
		}

		id := match[2]
		if prev, ok := queriesById[id]; ok {
			t.Errorf("%s: duplicate id %s", posn, id)
			t.Errorf("%s: previously used here", prev.posn)
			continue
		}

		selectRe, err := parseRegexp(match[3])
		if err != nil {
			t.Errorf("%s: %s", posn, err)
			continue
		}

		// Find text of the current line, sans query.
		// (Queries must be // not /**/ comments.)
		line := lines[posn.Line-1][:posn.Column-1]

		// Apply regexp to current line to find input selection.
		loc := selectRe.FindIndex(line)
		if loc == nil {
			t.Errorf("%s: selection pattern %s doesn't match line %q",
				posn, match[3], string(line))
			continue
		}

		// Assumes ASCII. TODO(adonovan): test on UTF-8.
		linestart := posn.Offset - (posn.Column - 1)

		// Compute the file offsets
		q := &query{
			id:       id,
			verb:     match[1],
			posn:     posn,
			filename: filename,
			start:    linestart + loc[0],
			end:      linestart + loc[1],
		}
		queries = append(queries, q)
		queriesById[id] = q
	}

	// Return the slice, not map, for deterministic iteration.
	return queries
}

// stripLocation removes a "file:line: " prefix.
func stripLocation(line string) string {
	if i := strings.Index(line, ": "); i >= 0 {
		line = line[i+2:]
	}
	return line
}

// doQuery poses query q to the oracle and writes its response and
// error (if any) to out.
func doQuery(out io.Writer, q *query) {
	fmt.Fprintf(out, "-------- @%s %s --------\n", q.verb, q.id)

	capture := new(bytes.Buffer) // capture standard output
	var buildContext = build.Default
	buildContext.GOPATH = "testdata"
	err := oracle.Main([]string{q.filename},
		q.verb,
		fmt.Sprintf("%s %d-%d", q.filename, q.start, q.end),
		/*PTA-log=*/ nil, capture, &buildContext)

	for _, line := range strings.Split(capture.String(), "\n") {
		fmt.Fprintf(out, "%s\n", stripLocation(line))
	}

	if err != nil {
		fmt.Fprintf(out, "Error: %s\n", stripLocation(err.Error()))
	}
}

func TestOracle(t *testing.T) {
	switch runtime.GOOS {
	case "windows":
		t.Skipf("skipping test on %q (no /usr/bin/diff)", runtime.GOOS)
	}

	for _, filename := range []string{
		"testdata/src/main/calls.go",
		"testdata/src/main/describe.go",
		"testdata/src/main/freevars.go",
		"testdata/src/main/implements.go",
		"testdata/src/main/imports.go",
		"testdata/src/main/peers.go",
	} {
		queries := parseQueries(t, filename)
		golden := filename + "lden"
		got := filename + "t"
		gotfh, err := os.Create(got)
		if err != nil {
{'fraction_non_alphanumeric': 0.15466972564764364, 'fraction_numerical': 0.0032501672880221777, 'mean_word_length': 3.1059654631083204, 'pattern_counts': {'":': 3, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7422256', 'n_tokens_mistral': 3891, 'n_tokens_neox': 3604, 'n_words': 1116}