<commit_before>// Copyright (c) 2018 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package codegen

import (
	"encoding/json"
	"fmt"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"

	"github.com/pkg/errors"
)

const (
	clientInterface = "Client"
	custom          = "custom"
)

// helper struct to pull out the fixture config
type moduleConfig struct {
	Config *config `json:"config"`
}

// config is the struct corresponding to the config field in client-config.json
type config struct {
	CustomImportPath string   `json:"customImportPath"`
	Fixture          *Fixture `json:"fixture"`
}

// Fixture specifies client fixture import path and all scenarios
type Fixture struct {
	// ImportPath is the package where the user-defined Fixture global variable is contained.
	// The Fixture object defines, for a given client, the standardized list of fixture scenarios for that client
	ImportPath string `json:"importPath"`
	// Scenarios is a map from zanzibar's exposed method name to a list of user-defined fixture scenarios for a client
	Scenarios map[string][]string `json:"scenarios"`
}

// Validate the fixture configuration
func (f *Fixture) Validate(methods map[string]interface{}) error {
	if f.ImportPath == "" {
		return errors.New("fixture importPath is empty")
	}
	for m := range f.Scenarios {
		if _, ok := methods[m]; !ok {
			return errors.Errorf("%q is not a valid method", m)
		}
	}
	return nil
}

// ClientMockGenHook returns a PostGenHook to generate client mocks
func ClientMockGenHook(h *PackageHelper, t *Template) (PostGenHook, error) {
	bin, err := NewMockgenBin(h, t)
	if err != nil {
		return nil, errors.Wrap(err, "error building mockgen binary")
	}

	return func(instances map[string][]*ModuleInstance) error {
		clientInstances := instances["client"]
		mockCount := len(clientInstances)
		fmt.Printf("Generating %d client mocks:\n", mockCount)

		importPathMap := make(map[string]string, mockCount)
		fixtureMap := make(map[string]*Fixture, mockCount)
		pathSymbolMap := make(map[string]string)
		for _, instance := range clientInstances {
			key := instance.ClassType + instance.InstanceName
			var mc moduleConfig
			if err := json.Unmarshal(instance.JSONFileRaw, &mc); err != nil {
				return errors.Wrapf(
					err,
					"error parsing client-config.json for client %q",
					instance.InstanceName,
				)
			}

			importPath := instance.PackageInfo.GeneratedPackagePath
			if instance.ClassType == custom {
				importPath = mc.Config.CustomImportPath
			}

			importPathMap[key] = importPath

			// gather all modules that need to generate fixture types
			f := mc.Config.Fixture
			if f != nil && f.Scenarios != nil {
				pathSymbolMap[importPath] = clientInterface
				fixtureMap[key] = f
			}
		}

		// only run reflect program once to gather interface info for all clients
		pkgs, err := ReflectInterface(bin.projRoot, pathSymbolMap)
		if err != nil {
			return errors.Wrap(err, "error parsing Client interfaces")
		}

		var idx int32 = 1
		var files sync.Map
		var wg sync.WaitGroup
		ic := make(chan *ModuleInstance)
		ec := make(chan error, mockCount)
		wg.Add(mockCount)

		// cap the num of goroutines to num of CPU, because
		// bin.GenMock in each goroutine starts a sub process
		n := runtime.GOMAXPROCS(0)
		if n > mockCount {
			n = mockCount
		}
		for i := 0; i < n; i++ {
			go func() {
				defer wg.Done()

				for {
					instance, ok := <-ic
					if !ok {
						return
					}

					key := instance.ClassType + instance.InstanceName
					buildDir := h.CodeGenTargetPath()
					genDir := filepath.Join(buildDir, instance.Directory, "mock-client")

					importPath := importPathMap[key]<commit_msg>Set waitgroup counter to number of workers<commit_after>// Copyright (c) 2018 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package codegen

import (
	"encoding/json"
	"fmt"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"

	"github.com/pkg/errors"
)

const (
	clientInterface = "Client"
	custom          = "custom"
)

// helper struct to pull out the fixture config
type moduleConfig struct {
	Config *config `json:"config"`
}

// config is the struct corresponding to the config field in client-config.json
type config struct {
	CustomImportPath string   `json:"customImportPath"`
	Fixture          *Fixture `json:"fixture"`
}

// Fixture specifies client fixture import path and all scenarios
type Fixture struct {
	// ImportPath is the package where the user-defined Fixture global variable is contained.
	// The Fixture object defines, for a given client, the standardized list of fixture scenarios for that client
	ImportPath string `json:"importPath"`
	// Scenarios is a map from zanzibar's exposed method name to a list of user-defined fixture scenarios for a client
	Scenarios map[string][]string `json:"scenarios"`
}

// Validate the fixture configuration
func (f *Fixture) Validate(methods map[string]interface{}) error {
	if f.ImportPath == "" {
		return errors.New("fixture importPath is empty")
	}
	for m := range f.Scenarios {
		if _, ok := methods[m]; !ok {
			return errors.Errorf("%q is not a valid method", m)
		}
	}
	return nil
}

// ClientMockGenHook returns a PostGenHook to generate client mocks
func ClientMockGenHook(h *PackageHelper, t *Template) (PostGenHook, error) {
	bin, err := NewMockgenBin(h, t)
	if err != nil {
		return nil, errors.Wrap(err, "error building mockgen binary")
	}

	return func(instances map[string][]*ModuleInstance) error {
		clientInstances := instances["client"]
		mockCount := len(clientInstances)
		fmt.Printf("Generating %d client mocks:\n", mockCount)

		importPathMap := make(map[string]string, mockCount)
		fixtureMap := make(map[string]*Fixture, mockCount)
		pathSymbolMap := make(map[string]string)
		for _, instance := range clientInstances {
			key := instance.ClassType + instance.InstanceName
			var mc moduleConfig
			if err := json.Unmarshal(instance.JSONFileRaw, &mc); err != nil {
				return errors.Wrapf(
					err,
					"error parsing client-config.json for client %q",
					instance.InstanceName,
				)
			}

			importPath := instance.PackageInfo.GeneratedPackagePath
			if instance.ClassType == custom {
				importPath = mc.Config.CustomImportPath
			}

			importPathMap[key] = importPath

			// gather all modules that need to generate fixture types
			f := mc.Config.Fixture
			if f != nil && f.Scenarios != nil {
				pathSymbolMap[importPath] = clientInterface
				fixtureMap[key] = f
			}
		}

		// only run reflect program once to gather interface info for all clients
		pkgs, err := ReflectInterface(bin.projRoot, pathSymbolMap)
		if err != nil {
			return errors.Wrap(err, "error parsing Client interfaces")
		}

		var idx int32 = 1
		var files sync.Map
		var wg sync.WaitGroup
		ic := make(chan *ModuleInstance)
		ec := make(chan error, mockCount)

		// cap the num of goroutines to num of CPU, because
		// bin.GenMock in each goroutine starts a sub process
		n := runtime.GOMAXPROCS(0)
		if n > mockCount {
			n = mockCount
		}
		wg.Add(n)

		for i := 0; i < n; i++ {
			go func() {
				defer wg.Done()

				for {
					instance, ok := <-ic
					if !ok {
						return
					}

					key := instance.ClassType + instance.InstanceName
					buildDir := h.CodeGenTargetPath()
					genDir := filepath.Join(buildDir, instance.Directory, "mock-client")

					importPath := importPathMap[key]
{'fraction_non_alphanumeric': 0.0973133065369156, 'fraction_numerical': 0.001903955997461392, 'mean_word_length': 3.666831194471866, 'pattern_counts': {'":': 0, '<': 7, '<?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': '6168200', 'n_tokens_mistral': 3047, 'n_tokens_neox': 2705, 'n_words': 1049}