github.com/bazelbuild/rules_webtesting@v0.2.0/go/metadata/web_test_files.go (about)

     1  // Copyright 2016 Google Inc.
     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 metadata
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"os/exec"
    21  	"path/filepath"
    22  	"sync"
    23  
    24  	"github.com/bazelbuild/rules_webtesting/go/bazel"
    25  )
    26  
    27  // WebTestFiles defines a set of namedFiles located either in the runfiles directory or
    28  // in an archive file located in the runfiles directory of the test.
    29  type WebTestFiles struct {
    30  	// ArchiveFile is optional path to an archive file (.zip, .tar.gz, .tgz, .tar.bz2, .tbz2, .tar.Z)
    31  	// file. If present, paths in NamedFiles are paths in the archive. If absent, paths in NamedFiles
    32  	// are relative to the runfiles root. The archive will only be extracted if getFilePath is called
    33  	// at least once with a name defined in NamedFiles. If so, the entire archive will be extracted
    34  	// into subdirectory located test tmpdir.
    35  	ArchiveFile string `json:"archiveFile,omitempty"`
    36  	// StripPrefix is an optional prefix that will be stripped when an archive is extracted.
    37  	StripPrefix string `json:"stripPrefix,omitempty"`
    38  	// NamedFiles is a map of names to file paths. These file paths are relative to the runfiles
    39  	// root if ArchiveFile is absent, otherwise they are paths inside the archive referred to by
    40  	// ArchiveFile. The names are used by other parts of Web Test Launcher to refer to needed
    41  	// resources. For example, if your environment needs to know where a chromedriver executable is
    42  	// located, then there could be a name "CHROMEDRIVER" that refers to the path to the chromedriver
    43  	// executable, and the part of you environment that needs to use the chromedriver executable
    44  	// can call md.GetFilePath("CHROMEDRIVER") (where md is a *metadata.Metadata object) which will
    45  	// search through all NamedFiles of all WebTestFiles structs in md to find that key and return
    46  	// the path to the corresponding file (extracting an archive if necessary).
    47  	NamedFiles map[string]string `json:"namedFiles"`
    48  
    49  	// The mu field protects access to the extractedPath field.
    50  	mu sync.Mutex
    51  	// The extractedPath field refers to the location where this archive has been extracted to, if
    52  	// has been extracted.
    53  	extractedPath string
    54  }
    55  
    56  func normalizeWebTestFiles(in []*WebTestFiles) ([]*WebTestFiles, error) {
    57  	merged := map[string]*WebTestFiles{}
    58  
    59  	for _, a := range in {
    60  		// skip entries with no named files.
    61  		if len(a.NamedFiles) == 0 {
    62  			continue
    63  		}
    64  		if b := merged[a.ArchiveFile]; b != nil {
    65  			m, err := mergeWebTestFiles(a, b)
    66  			if err != nil {
    67  				return nil, err
    68  			}
    69  			merged[m.ArchiveFile] = m
    70  		} else {
    71  			merged[a.ArchiveFile] = a
    72  		}
    73  	}
    74  
    75  	names := map[string]bool{}
    76  	var result []*WebTestFiles
    77  	for _, m := range merged {
    78  		for name := range m.NamedFiles {
    79  			if names[name] {
    80  				return nil, fmt.Errorf("name %q exists in multiple WebTestFiles", name)
    81  			}
    82  			names[name] = true
    83  		}
    84  		result = append(result, m)
    85  	}
    86  	return result, nil
    87  }
    88  
    89  func mergeWebTestFiles(a1, a2 *WebTestFiles) (*WebTestFiles, error) {
    90  	if a1.ArchiveFile != a2.ArchiveFile {
    91  		return nil, fmt.Errorf("expected paths (%q, %q) to be equal", a1.ArchiveFile, a2.ArchiveFile)
    92  	}
    93  	nf, err := mergeNamedFiles(a1.NamedFiles, a2.NamedFiles)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	return &WebTestFiles{
    98  		ArchiveFile: a1.ArchiveFile,
    99  		NamedFiles:  nf,
   100  	}, nil
   101  }
   102  
   103  func mergeNamedFiles(n1, n2 map[string]string) (map[string]string, error) {
   104  	result := map[string]string{}
   105  
   106  	for k, v := range n1 {
   107  		result[k] = v
   108  	}
   109  
   110  	for k, v2 := range n2 {
   111  		if v1, ok := result[k]; ok && v1 != v2 {
   112  			return nil, fmt.Errorf("key %q exists in both NamedFiles with different values", k)
   113  		}
   114  		result[k] = v2
   115  	}
   116  	return result, nil
   117  }
   118  
   119  func (w *WebTestFiles) getFilePath(name string, m *Metadata) (string, error) {
   120  	filename, ok := w.NamedFiles[name]
   121  	if !ok {
   122  		return "", nil
   123  	}
   124  
   125  	if w.ArchiveFile == "" {
   126  		return bazel.Runfile(filename)
   127  	}
   128  
   129  	if err := w.extract(m); err != nil {
   130  		return "", err
   131  	}
   132  
   133  	path := filepath.Join(w.extractedPath, filename)
   134  	if _, err := os.Stat(path); err != nil {
   135  		return "", err
   136  	}
   137  	return path, nil
   138  }
   139  
   140  func (w *WebTestFiles) extract(m *Metadata) error {
   141  	w.mu.Lock()
   142  	defer w.mu.Unlock()
   143  	if w.extractedPath != "" {
   144  		return nil
   145  	}
   146  
   147  	extractor, err := m.GetFilePath("EXTRACT_EXE")
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	filename, err := bazel.Runfile(w.ArchiveFile)
   153  	if err != nil {
   154  		return err
   155  	}
   156  
   157  	extractPath, err := bazel.NewTmpDir(filepath.Base(filename))
   158  	if err != nil {
   159  		return err
   160  	}
   161  
   162  	c := exec.Command(extractor, filename, extractPath, w.StripPrefix)
   163  
   164  	if err := c.Run(); err != nil {
   165  		return err
   166  	}
   167  
   168  	w.extractedPath = extractPath
   169  	return nil
   170  }