chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
// Copyright 2023 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dolthub/vitess/go/vt/sqlparser"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/dolthub/doltgresql/postgres/parser/parser"
|
||||
"github.com/dolthub/doltgresql/server/ast"
|
||||
"github.com/dolthub/doltgresql/testing/generation/utils"
|
||||
)
|
||||
|
||||
const TestHeader = `// Copyright %d Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package output
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test%s(t *testing.T) {
|
||||
tests := []QueryParses{
|
||||
`
|
||||
|
||||
const TestFooter = ` }
|
||||
RunTests(t, tests)
|
||||
}
|
||||
`
|
||||
|
||||
const MaxTestCount = 10000
|
||||
|
||||
// GenerateTestsFromSynopses generates a test file in the output directory for each file in the synopses directory.
|
||||
func GenerateTestsFromSynopses(repetitionDisabled ...string) (err error) {
|
||||
parentFolder, err := GetCommandDocsFolder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileInfos, err := parentFolder.ReadDir("synopses")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
removeComments := regexp.MustCompile(`\/\/[^\r\n]*\r?\n?`)
|
||||
|
||||
FileLoop:
|
||||
for _, fileInfo := range fileInfos {
|
||||
prefix := strings.ToUpper(
|
||||
strings.ReplaceAll(
|
||||
strings.ReplaceAll(
|
||||
fileInfo.Name(),
|
||||
".txt",
|
||||
"",
|
||||
),
|
||||
"_",
|
||||
" ",
|
||||
),
|
||||
)
|
||||
utils.PrintSectionMarker(prefix, '+', 80)
|
||||
data, nErr := parentFolder.ReadFileFromDirectory("synopses", fileInfo.Name())
|
||||
if nErr != nil {
|
||||
err = errors.Join(err, nErr)
|
||||
continue FileLoop
|
||||
}
|
||||
dataStr := strings.TrimSpace(string(data))
|
||||
scanner := NewScanner(dataStr)
|
||||
tokens, nErr := scanner.Process()
|
||||
if nErr != nil {
|
||||
err = errors.Join(err, nErr)
|
||||
continue FileLoop
|
||||
}
|
||||
scannerString := scanner.String()
|
||||
if removeComments.ReplaceAllString(dataStr, "") != scannerString {
|
||||
sb := strings.Builder{}
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(dataStr, scannerString, true)
|
||||
whitespaceOnly := true
|
||||
for _, diff := range diffs {
|
||||
if diff.Type != diffmatchpatch.DiffEqual && diff.Text != " " && diff.Text != "\n" {
|
||||
whitespaceOnly = false
|
||||
}
|
||||
}
|
||||
if whitespaceOnly {
|
||||
sb.WriteString(utils.SectionMarker("Whitespace Differences", '-', 80))
|
||||
} else {
|
||||
sb.WriteString(dmp.DiffPrettyText(diffs))
|
||||
}
|
||||
fmt.Println(sb.String())
|
||||
err = errors.Join(err, errors.New(sb.String()))
|
||||
continue FileLoop
|
||||
}
|
||||
includeRepetition := len(repetitionDisabled) == 0 || repetitionDisabled[0] != "*"
|
||||
for _, bans := range repetitionDisabled {
|
||||
if strings.EqualFold(bans, prefix) {
|
||||
includeRepetition = false
|
||||
break
|
||||
}
|
||||
}
|
||||
stmtGen, nErr := utils.ParseTokens(tokens, includeRepetition)
|
||||
if nErr != nil {
|
||||
err = errors.Join(err, nErr)
|
||||
continue FileLoop
|
||||
}
|
||||
// Not all variables have their definitions set in the synopsis, so we'll handle them here
|
||||
unsetVariables, nErr := utils.UnsetVariables(stmtGen)
|
||||
if nErr != nil {
|
||||
err = errors.Join(err, nErr)
|
||||
continue FileLoop
|
||||
}
|
||||
customVariableDefinitions := make(map[string]utils.StatementGenerator)
|
||||
for _, unsetVariable := range unsetVariables {
|
||||
// Check for a specific definition first
|
||||
if prefixVariables, ok := PrefixCustomVariables[prefix]; ok {
|
||||
if variableDefinition, ok := prefixVariables[unsetVariable]; ok {
|
||||
customVariableDefinitions[unsetVariable] = variableDefinition
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Check the global definitions if there isn't a specific definition
|
||||
if variableDefinition, ok := GlobalCustomVariables[unsetVariable]; ok {
|
||||
customVariableDefinitions[unsetVariable] = variableDefinition
|
||||
continue
|
||||
}
|
||||
}
|
||||
if nErr = utils.ApplyVariableDefinition(stmtGen, customVariableDefinitions); nErr != nil {
|
||||
err = errors.Join(err, nErr)
|
||||
continue FileLoop
|
||||
}
|
||||
sb := strings.Builder{}
|
||||
sb.WriteString(fmt.Sprintf(TestHeader, time.Now().Year(),
|
||||
strings.ReplaceAll(cases.Title(language.English).String(strings.ToLower(prefix)), " ", "")))
|
||||
|
||||
permutations := stmtGen.Permutations()
|
||||
if permutations.Cmp(big.NewInt(MaxTestCount)) <= 0 {
|
||||
result, nErr := GetQueryResult(stmtGen.String())
|
||||
if nErr != nil {
|
||||
err = errors.Join(err, nErr)
|
||||
continue FileLoop
|
||||
}
|
||||
sb.WriteString(result)
|
||||
for stmtGen.Consume() {
|
||||
result, nErr = GetQueryResult(stmtGen.String())
|
||||
if nErr != nil {
|
||||
err = errors.Join(err, nErr)
|
||||
continue FileLoop
|
||||
}
|
||||
sb.WriteString(result)
|
||||
}
|
||||
} else {
|
||||
randomInts, nErr := utils.GenerateRandomInts(MaxTestCount, permutations)
|
||||
if nErr != nil {
|
||||
err = errors.Join(err, nErr)
|
||||
}
|
||||
for _, randomInt := range randomInts {
|
||||
stmtGen.SetConsumeIterations(randomInt)
|
||||
result, nErr := GetQueryResult(stmtGen.String())
|
||||
if nErr != nil {
|
||||
err = errors.Join(err, nErr)
|
||||
continue FileLoop
|
||||
}
|
||||
sb.WriteString(result)
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteString(TestFooter)
|
||||
outputFileName := strings.ToLower(strings.ReplaceAll(prefix, " ", "_"))
|
||||
if nErr = parentFolder.WriteFileToDirectory("output", outputFileName+"_test.go", []byte(sb.String()), 0644); nErr != nil {
|
||||
err = errors.Join(err, nErr)
|
||||
continue FileLoop
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var postgresVerificationConnection *pgx.Conn
|
||||
|
||||
// GetQueryResult runs the query against a Postgres server to validate that the query is syntactically valid. It then
|
||||
// tests the query against the Postgres parser and Postgres-Vitess AST converter to check the current level of support.
|
||||
// It returns a string that may be inserted directly into a test source file (two tabs are prefixed).
|
||||
func GetQueryResult(query string) (string, error) {
|
||||
var err error
|
||||
ctx := context.Background()
|
||||
if postgresVerificationConnection == nil {
|
||||
connectionString := fmt.Sprintf("postgres://postgres:password@127.0.0.1:%d/", 5432)
|
||||
postgresVerificationConnection, err = pgx.Connect(ctx, connectionString)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
testQuery := fmt.Sprintf("DO $SYNTAX_CHECK$ BEGIN RETURN; %s; END; $SYNTAX_CHECK$;", query)
|
||||
_, err = postgresVerificationConnection.Exec(ctx, testQuery)
|
||||
if err != nil && strings.Contains(err.Error(), "syntax error") {
|
||||
// We only care about syntax errors, as statements may rely on internal state, which is not what we're testing
|
||||
// There are statements that will not execute inside our DO block due to how Postgres handles some queries, so
|
||||
// to confirm that they're syntax errors, we'll run them outside the block. All such queries should be
|
||||
// non-destructive, so this should be safe. All other queries will still return a syntax error.
|
||||
_, err = postgresVerificationConnection.Exec(ctx, query)
|
||||
// Run a ROLLBACK as some commands may put the connection (not the database) in a bad state
|
||||
_, _ = postgresVerificationConnection.Exec(ctx, "ROLLBACK;")
|
||||
if err != nil && strings.Contains(err.Error(), "syntax error") {
|
||||
return "", fmt.Errorf("%s\n%s", err, query)
|
||||
}
|
||||
}
|
||||
formattedQuery := strings.ReplaceAll(query, `"`, `\"`)
|
||||
statements, err := parser.Parse(query)
|
||||
if err != nil || len(statements) == 0 {
|
||||
return fmt.Sprintf("\t\tUnimplemented(\"%s\"),\n", formattedQuery), nil
|
||||
}
|
||||
for _, statement := range statements {
|
||||
vitessAST, err := func() (vitessAST sqlparser.Statement, err error) {
|
||||
defer func() {
|
||||
if recoverVal := recover(); recoverVal != nil {
|
||||
vitessAST = nil
|
||||
}
|
||||
}()
|
||||
return ast.Convert(statement)
|
||||
}()
|
||||
if err != nil || vitessAST == nil {
|
||||
return fmt.Sprintf("\t\tParses(\"%s\"),\n", formattedQuery), nil
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("\t\tConverts(\"%s\"),\n", formattedQuery), nil
|
||||
}
|
||||
Reference in New Issue
Block a user