cuelang.org/go@v0.13.0/encoding/jsonschema/vendor_external.go (about)

     1  // Copyright 2024 CUE 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  //go:build ignore
    16  
    17  // This command copies external JSON Schema tests into the local
    18  // repository. It tries to maintain existing test-skip information
    19  // to avoid unintentional regressions.
    20  
    21  package main
    22  
    23  import (
    24  	"archive/zip"
    25  	"bytes"
    26  	"errors"
    27  	"flag"
    28  	"fmt"
    29  	"io"
    30  	"io/fs"
    31  	"log"
    32  	"net/http"
    33  	"os"
    34  	"path"
    35  	"path/filepath"
    36  	"strings"
    37  
    38  	"cuelang.org/go/encoding/jsonschema/internal/externaltest"
    39  )
    40  
    41  const (
    42  	testRepo = "https://github.com/json-schema-org/JSON-Schema-Test-Suite.git"
    43  	testDir  = "testdata/external"
    44  )
    45  
    46  func main() {
    47  	flag.Usage = func() {
    48  		fmt.Fprintf(os.Stderr, "usage: vendor-external commit\n")
    49  		os.Exit(2)
    50  	}
    51  	log.SetFlags(log.Lshortfile | log.Ltime | log.Lmicroseconds)
    52  	flag.Parse()
    53  	if flag.NArg() != 1 {
    54  		flag.Usage()
    55  	}
    56  	if err := doVendor(flag.Arg(0)); err != nil {
    57  		log.Fatal(err)
    58  	}
    59  }
    60  
    61  func doVendor(commit string) error {
    62  	// Fetch a commit from GitHub via their archive ZIP endpoint, which is a lot faster
    63  	// than git cloning just to retrieve a single commit's files.
    64  	// See: https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28#download-a-repository-archive-zip
    65  	zipURL := fmt.Sprintf("https://github.com/json-schema-org/JSON-Schema-Test-Suite/archive/%s.zip", commit)
    66  	log.Printf("fetching %s", zipURL)
    67  	resp, err := http.Get(zipURL)
    68  	if err != nil {
    69  		return err
    70  	}
    71  	defer resp.Body.Close()
    72  	zipBytes, err := io.ReadAll(resp.Body)
    73  	if err != nil {
    74  		return err
    75  	}
    76  
    77  	log.Printf("reading old test data")
    78  	oldTests, err := externaltest.ReadTestDir(testDir)
    79  	if err != nil && !errors.Is(err, externaltest.ErrNotFound) {
    80  		return err
    81  	}
    82  
    83  	log.Printf("copying files to %s", testDir)
    84  	testSubdir := filepath.Join(testDir, "tests")
    85  	if err := os.RemoveAll(testSubdir); err != nil {
    86  		return err
    87  	}
    88  	zipr, err := zip.NewReader(bytes.NewReader(zipBytes), int64(len(zipBytes)))
    89  	if err != nil {
    90  		return err
    91  	}
    92  	// Note that GitHub produces archives with a top-level directory representing
    93  	// the name of the repository and the version which was retrieved.
    94  	fsys, err := fs.Sub(zipr, fmt.Sprintf("JSON-Schema-Test-Suite-%s/tests", commit))
    95  	if err != nil {
    96  		return err
    97  	}
    98  	err = fs.WalkDir(fsys, ".", func(filename string, d fs.DirEntry, err error) error {
    99  		if err != nil {
   100  			return err
   101  		}
   102  		// Exclude draft-next (too new) and draft3 (too old).
   103  		if d.IsDir() && (filename == "draft-next" || filename == "draft3") {
   104  			return fs.SkipDir
   105  		}
   106  		// Exclude symlinks and directories
   107  		if !d.Type().IsRegular() {
   108  			return nil
   109  		}
   110  		if !strings.HasSuffix(filename, ".json") {
   111  			return nil
   112  		}
   113  		if err := os.MkdirAll(filepath.Join(testSubdir, path.Dir(filename)), 0o777); err != nil {
   114  			return err
   115  		}
   116  		data, err := fs.ReadFile(fsys, filename)
   117  		if err != nil {
   118  			return err
   119  		}
   120  		if err := os.WriteFile(filepath.Join(testSubdir, filename), data, 0o666); err != nil {
   121  			return err
   122  		}
   123  		return nil
   124  	})
   125  	if err != nil {
   126  		return err
   127  	}
   128  
   129  	// Read the test data back that we've just written and attempt
   130  	// to populate skip data from the original test data.
   131  	// As indexes are not necessarily stable (new test cases
   132  	// might be inserted in the middle of an array), we try
   133  	// first to look up the skip info by JSON data, and then
   134  	// by test description.
   135  	byJSON := make(map[skipKey]externaltest.Skip)
   136  	byDescription := make(map[skipKey]externaltest.Skip)
   137  
   138  	for filename, schemas := range oldTests {
   139  		for _, schema := range schemas {
   140  			byJSON[skipKey{filename, string(schema.Schema), ""}] = schema.Skip
   141  			byDescription[skipKey{filename, schema.Description, ""}] = schema.Skip
   142  			for _, test := range schema.Tests {
   143  				byJSON[skipKey{filename, string(schema.Schema), string(test.Data)}] = test.Skip
   144  				byDescription[skipKey{filename, schema.Description, test.Description}] = schema.Skip
   145  			}
   146  		}
   147  	}
   148  
   149  	newTests, err := externaltest.ReadTestDir(testDir)
   150  	if err != nil {
   151  		return err
   152  	}
   153  
   154  	for filename, schemas := range newTests {
   155  		for _, schema := range schemas {
   156  			skip, ok := byJSON[skipKey{filename, string(schema.Schema), ""}]
   157  			if !ok {
   158  				skip, _ = byDescription[skipKey{filename, schema.Description, ""}]
   159  			}
   160  			schema.Skip = skip
   161  			for _, test := range schema.Tests {
   162  				skip, ok := byJSON[skipKey{filename, string(schema.Schema), string(test.Data)}]
   163  				if !ok {
   164  					skip, _ = byDescription[skipKey{filename, schema.Description, test.Description}]
   165  				}
   166  				test.Skip = skip
   167  			}
   168  		}
   169  	}
   170  	if err := externaltest.WriteTestDir(testDir, newTests); err != nil {
   171  		return err
   172  	}
   173  	log.Printf("finished")
   174  	return nil
   175  }
   176  
   177  type skipKey struct {
   178  	filename string
   179  	schema   string
   180  	test     string
   181  }