<commit_before>package bloomskyStructure

import (
	"encoding/json"
	"fmt"
	"time"

	config "github.com/patrickalin/GoBloomsky/config"

	mylog "github.com/patrickalin/GoMyLog"
	rest "github.com/patrickalin/GoRest"
)

// generate by http://mervine.net/json2struct

// BloomskyStructure represent the structure of the JSON return by the API
type BloomskyStructure struct {
	UTC              float64                `json:"UTC"`
	CityName         string                 `json:"CityName"`
	Storm            BloomskyStormStructure `json:"Storm"`
	Searchable       bool                   `json:"Searchable"`
	DeviceName       string                 `json:"DeviceName"`
	RegisterTime     float64                `json:"RegisterTime"`
	DST              float64                `json:"DST"`
	BoundedPoint     string                 `json:"BoundedPoint"`
	LON              float64                `json:"LON"`
	Point            interface{}            `json:"Point"`
	VideoList        []string               `json:"VideoList"`
	VideoListC       []string               `json:"VideoList_C"`
	DeviceID         string                 `json:"DeviceID"`
	NumOfFollowers   float64                `json:"NumOfFollowers"`
	LAT              float64                `json:"LAT"`
	ALT              float64                `json:"ALT"`
	Data             BloomskyDataStructure  `json:"Data"`
	FullAddress      string                 `json:"FullAddress"`
	StreetName       string                 `json:"StreetName"`
	PreviewImageList []string               `json:"PreviewImageList"`
}

// BloomskyStormStructure represent the structure STORM of the JSON return by the API
type BloomskyStormStructure struct {
	UVIndex            string  `json:"UVIndex"`
	WindDirection      string  `json:"WindDirection"`
	RainDaily          float64 `json:"RainDaily"`
	WindGust           float64 `json:"WindGust"`
	SustainedWindSpeed float64 `json:"SustainedWindSpeed"`
	RainRate           float64 `json:"RainRate"`
	Rain               float64 `json:"24hRain"`
}

// BloomskyDataStructure represent the structure SKY of the JSON return by the API
type BloomskyDataStructure struct {
	Luminance   float64 `json:"Luminance"`
	Temperature float64 `json:"Temperature"`
	ImageURL    string  `json:"ImageURL"`
	TS          float64 `json:"TS"`
	Rain        bool    `json:"Rain"`
	Humidity    float64 `json:"Humidity"`
	Pressure    float64 `json:"Pressure"`
	DeviceType  string  `json:"DeviceType"`
	Voltage     float64 `json:"Voltage"`
	Night       bool    `json:"Night"`
	UVIndex     float64 `json:"UVIndex"`
	ImageTS     float64 `json:"ImageTS"`
}

// bloomskyStructure is the interface bloomskyStructure
type bloomskyStructure interface {
	GetDeviceID() string
	GetSoftwareVersion() string
	GetAmbientTemperatureC() float64
	GetTargetTemperatureC() float64
	GetAmbientTemperatureF() float64
	GetTargetTemperatureF() float64
	GetHumidity() float64
	GetAway() string
	ShowPrettyAll() int
}

type bloomskyError struct {
	message error
	advice  string
}

func (e *bloomskyError) Error() string {
	return fmt.Sprintf("\n \t bloomskyError :> %s \n\t Advice :> %s", e.message, e.advice)
}

// ShowPrettyAll prints to the console the JSON
func (bloomskyInfo BloomskyStructure) ShowPrettyAll() int {
	out, err := json.Marshal(bloomskyInfo)
	if err != nil {
		fmt.Println("Error with parsing Json")
		mylog.Error.Fatal(err)
	}
	mylog.Trace.Printf("Decode:> \n %s \n\n", out)
	return 0
}

//GetTimeStamp returns the timestamp give by Bloomsky
func (bloomskyInfo BloomskyStructure) GetTimeStamp() time.Time {
	tm := time.Unix(int64(bloomskyInfo.Data.TS), 0)
	return tm
}

//GetCity returns the city name
func (bloomskyInfo BloomskyStructure) GetCity() string {
	return bloomskyInfo.CityName
}

//GetDevideId returns the Device Id
func (bloomskyInfo BloomskyStructure) GetDeviceID() string {
	return bloomskyInfo.DeviceID
}

//GetNumOfFollowers returns the number of followers
func (bloomskyInfo BloomskyStructure) GetNumOfFollowers() int {
	return int(bloomskyInfo.NumOfFollowers)
}

//GetIndexUV returns the UV index from 1 to 11
func (bloomskyInfo BloomskyStructure) GetIndexUV() string {
	return bloomskyInfo.Storm.UVIndex
}

//IsNight returns true if it's the night
func (bloomskyInfo BloomskyStructure) IsNight() bool {
	return bloomskyInfo.Data.Night
}

//GetTemperatureFahrenheit returns temperature in Fahrenheit
func (bloomskyInfo BloomskyStructure) GetTemperatureFahrenheit() float64 {
	return bloomskyInfo.Data.Temperature
}

//GetTemperatureCelcius returns temperature in Celcius
func (bloomskyInfo BloomskyStructure) GetTemperatureCelcius() float64 {
	return ((bloomskyInfo.Data.Temperature - 32.00) * 5.00 / 9.00)
}

//GetHumidity returns hulidity %
func (bloomskyInfo BloomskyStructure) GetHumidity() float64 {
	return bloomskyInfo.Data.Humidity
}

//GetPressureHPa returns pressure in HPa
func (bloomskyInfo BloomskyStructure) GetPressureHPa() float64 {
	return (bloomskyInfo.Data.Pressure * 33.8638815)
}

//GetPressureInHg returns pressure in InHg
func (bloomskyInfo BloomskyStructure) GetPressureInHg() float64 {
	return bloomskyInfo.Data.Pressure
}

//GetWindDirection returns wind direction (N,S,W,E, ...)
func (bloomskyInfo BloomskyStructure) GetWindDirection() string {
	return bloomskyInfo.Storm.WindDirection
}

//GetWindGustMph returns Wind in Mph
func (bloomskyInfo BloomskyStructure) GetWindGustMph() float64 {
	return bloomskyInfo.Storm.WindGust
}

//GetWindGustMs returns Wind in Ms
func (bloomskyInfo BloomskyStructure) GetWindGustMs() float64 {
	return (bloomskyInfo.Storm.WindGust * 1.61)
}

//GetSustainedWindSpeedMph returns Sustained Wind Speed in Mph
func (bloomskyInfo BloomskyStructure) GetSustainedWindSpeedMph() float64 {
	return bloomskyInfo.Storm.SustainedWindSpeed
}

//GetSustainedWindSpeedMs returns Sustained Wind Speed in Ms
func (bloomskyInfo BloomskyStructure) GetSustainedWindSpeedMs() float64 {
	return (bloomskyInfo.Storm.SustainedWindSpeed * 1.61)
}

//IsRain returns true if it's rain
func (bloomskyInfo BloomskyStructure) IsRain() bool {
	return bloomskyInfo.Data.Rain
}

//GetRainDailyIn returns rain daily in In
func (bloomskyInfo BloomskyStructure) GetRainDailyIn() float64 {
	return bloomskyInfo.Storm.RainDaily
}

//GetRainIn returns total rain in In
func (bloomskyInfo BloomskyStructure) GetRainIn() float64 {
	return bloomskyInfo.Storm.Rain
}

//GetRainRateIn returns rain in In
func (bloomskyInfo BloomskyStructure) GetRainRateIn() float64 {
	return bloomskyInfo.Storm.RainRate
}

//GetRainDailyMm returns rain daily in mm
func (bloomskyInfo BloomskyStructure) GetRainDailyMm() float64 {
	return bloomskyInfo.Storm.RainDaily
}

//GetRainMm returns total rain in mm
func (bloomskyInfo BloomskyStructure) GetRainMm() float64 {
	return bloomskyInfo.Storm.Rain
}

//GetRainRateMm returns rain in mm
func (bloomskyInfo BloomskyStructure) GetRainRateMm() float64 {
	return bloomskyInfo.Storm.RainRate
}

/*
	DeviceType  string  `json:"DeviceType"`
	Voltage     float64 `json:"Voltage"`
*/

// MakeNew calls bloomsky and get structurebloomsky
func MakeNew(oneConfig config.ConfigStructure) BloomskyStructure {

	var retry = 0
	var err error
	var duration = time.Minute * 5

	// get body from Rest API
	mylog.Trace.Printf("Get from Rest bloomsky API")
	myRest := rest.MakeNew()

	b := []string{oneConfig.BloomskyAccessToken}

	var m map[string][]string
	m = make(map[string][]string)
	m["Authorization"] = b

	for retry < 5 {
		err = myRest.GetWithHeaders(oneConfig.BloomskyURL, m)
		if err != nil {
			mylog.Error.Println(&bloomskyError{err, "Problem with call rest, check the URL and the secret ID in the config file"})
			retry++
			time.Sleep(duration)
		} else {
			retry = 5
		}
	}

	if err != nil {
		mylog.Error.Fatal(&bloomskyError{err, "Problem with call rest, check the URL and the secret ID in the config file"})
	}

	var bloomskyInfo []BloomskyStructure

	body := myRest.GetBody()

	mylog.Trace.Printf("Unmarshal the response")
	err = json.Unmarshal(body, &bloomskyInfo)

	if err != nil {
		mylog.Error.Fatal(&bloomskyError{err, "Problem with json to struct, problem in the struct ?"})
	}

	bloomskyInfo[0].ShowPrettyAll()

	return bloomskyInfo[0]
}
<commit_msg>misspell celcius<commit_after>package bloomskyStructure

import (
	"encoding/json"
	"fmt"
	"time"

	config "github.com/patrickalin/GoBloomsky/config"

	mylog "github.com/patrickalin/GoMyLog"
	rest "github.com/patrickalin/GoRest"
)

// generate by http://mervine.net/json2struct

// BloomskyStructure represent the structure of the JSON return by the API
type BloomskyStructure struct {
	UTC              float64                `json:"UTC"`
	CityName         string                 `json:"CityName"`
	Storm            BloomskyStormStructure `json:"Storm"`
	Searchable       bool                   `json:"Searchable"`
	DeviceName       string                 `json:"DeviceName"`
	RegisterTime     float64                `json:"RegisterTime"`
	DST              float64                `json:"DST"`
	BoundedPoint     string                 `json:"BoundedPoint"`
	LON              float64                `json:"LON"`
	Point            interface{}            `json:"Point"`
	VideoList        []string               `json:"VideoList"`
	VideoListC       []string               `json:"VideoList_C"`
	DeviceID         string                 `json:"DeviceID"`
	NumOfFollowers   float64                `json:"NumOfFollowers"`
	LAT              float64                `json:"LAT"`
	ALT              float64                `json:"ALT"`
	Data             BloomskyDataStructure  `json:"Data"`
	FullAddress      string                 `json:"FullAddress"`
	StreetName       string                 `json:"StreetName"`
	PreviewImageList []string               `json:"PreviewImageList"`
}

// BloomskyStormStructure represent the structure STORM of the JSON return by the API
type BloomskyStormStructure struct {
	UVIndex            string  `json:"UVIndex"`
	WindDirection      string  `json:"WindDirection"`
	RainDaily          float64 `json:"RainDaily"`
	WindGust           float64 `json:"WindGust"`
	SustainedWindSpeed float64 `json:"SustainedWindSpeed"`
	RainRate           float64 `json:"RainRate"`
	Rain               float64 `json:"24hRain"`
}

// BloomskyDataStructure represent the structure SKY of the JSON return by the API
type BloomskyDataStructure struct {
	Luminance   float64 `json:"Luminance"`
	Temperature float64 `json:"Temperature"`
	ImageURL    string  `json:"ImageURL"`
	TS          float64 `json:"TS"`
	Rain        bool    `json:"Rain"`
	Humidity    float64 `json:"Humidity"`
	Pressure    float64 `json:"Pressure"`
	DeviceType  string  `json:"DeviceType"`
	Voltage     float64 `json:"Voltage"`
	Night       bool    `json:"Night"`
	UVIndex     float64 `json:"UVIndex"`
	ImageTS     float64 `json:"ImageTS"`
}

// bloomskyStructure is the interface bloomskyStructure
type bloomskyStructure interface {
	GetDeviceID() string
	GetSoftwareVersion() string
	GetAmbientTemperatureC() float64
	GetTargetTemperatureC() float64
	GetAmbientTemperatureF() float64
	GetTargetTemperatureF() float64
	GetHumidity() float64
	GetAway() string
	ShowPrettyAll() int
}

type bloomskyError struct {
	message error
	advice  string
}

func (e *bloomskyError) Error() string {
	return fmt.Sprintf("\n \t bloomskyError :> %s \n\t Advice :> %s", e.message, e.advice)
}

// ShowPrettyAll prints to the console the JSON
func (bloomskyInfo BloomskyStructure) ShowPrettyAll() int {
	out, err := json.Marshal(bloomskyInfo)
	if err != nil {
		fmt.Println("Error with parsing Json")
		mylog.Error.Fatal(err)
	}
	mylog.Trace.Printf("Decode:> \n %s \n\n", out)
	return 0
}

//GetTimeStamp returns the timestamp give by Bloomsky
func (bloomskyInfo BloomskyStructure) GetTimeStamp() time.Time {
	tm := time.Unix(int64(bloomskyInfo.Data.TS), 0)
	return tm
}

//GetCity returns the city name
func (bloomskyInfo BloomskyStructure) GetCity() string {
	return bloomskyInfo.CityName
}

//GetDevideId returns the Device Id
func (bloomskyInfo BloomskyStructure) GetDeviceID() string {
	return bloomskyInfo.DeviceID
}

//GetNumOfFollowers returns the number of followers
func (bloomskyInfo BloomskyStructure) GetNumOfFollowers() int {
	return int(bloomskyInfo.NumOfFollowers)
}

//GetIndexUV returns the UV index from 1 to 11
func (bloomskyInfo BloomskyStructure) GetIndexUV() string {
	return bloomskyInfo.Storm.UVIndex
}

//IsNight returns true if it's the night
func (bloomskyInfo BloomskyStructure) IsNight() bool {
	return bloomskyInfo.Data.Night
}

//GetTemperatureFahrenheit returns temperature in Fahrenheit
func (bloomskyInfo BloomskyStructure) GetTemperatureFahrenheit() float64 {
	return bloomskyInfo.Data.Temperature
}

//GetTemperatureCelsius returns temperature in Celsius
func (bloomskyInfo BloomskyStructure) GetTemperatureCelsius() float64 {
	return ((bloomskyInfo.Data.Temperature - 32.00) * 5.00 / 9.00)
}

//GetHumidity returns hulidity %
func (bloomskyInfo BloomskyStructure) GetHumidity() float64 {
	return bloomskyInfo.Data.Humidity
}

//GetPressureHPa returns pressure in HPa
func (bloomskyInfo BloomskyStructure) GetPressureHPa() float64 {
	return (bloomskyInfo.Data.Pressure * 33.8638815)
}

//GetPressureInHg returns pressure in InHg
func (bloomskyInfo BloomskyStructure) GetPressureInHg() float64 {
	return bloomskyInfo.Data.Pressure
}

//GetWindDirection returns wind direction (N,S,W,E, ...)
func (bloomskyInfo BloomskyStructure) GetWindDirection() string {
	return bloomskyInfo.Storm.WindDirection
}

//GetWindGustMph returns Wind in Mph
func (bloomskyInfo BloomskyStructure) GetWindGustMph() float64 {
	return bloomskyInfo.Storm.WindGust
}

//GetWindGustMs returns Wind in Ms
func (bloomskyInfo BloomskyStructure) GetWindGustMs() float64 {
	return (bloomskyInfo.Storm.WindGust * 1.61)
}

//GetSustainedWindSpeedMph returns Sustained Wind Speed in Mph
func (bloomskyInfo BloomskyStructure) GetSustainedWindSpeedMph() float64 {
	return bloomskyInfo.Storm.SustainedWindSpeed
}

//GetSustainedWindSpeedMs returns Sustained Wind Speed in Ms
func (bloomskyInfo BloomskyStructure) GetSustainedWindSpeedMs() float64 {
	return (bloomskyInfo.Storm.SustainedWindSpeed * 1.61)
}

//IsRain returns true if it's rain
func (bloomskyInfo BloomskyStructure) IsRain() bool {
	return bloomskyInfo.Data.Rain
}

//GetRainDailyIn returns rain daily in In
func (bloomskyInfo BloomskyStructure) GetRainDailyIn() float64 {
	return bloomskyInfo.Storm.RainDaily
}

//GetRainIn returns total rain in In
func (bloomskyInfo BloomskyStructure) GetRainIn() float64 {
	return bloomskyInfo.Storm.Rain
}

//GetRainRateIn returns rain in In
func (bloomskyInfo BloomskyStructure) GetRainRateIn() float64 {
	return bloomskyInfo.Storm.RainRate
}

//GetRainDailyMm returns rain daily in mm
func (bloomskyInfo BloomskyStructure) GetRainDailyMm() float64 {
	return bloomskyInfo.Storm.RainDaily
}

//GetRainMm returns total rain in mm
func (bloomskyInfo BloomskyStructure) GetRainMm() float64 {
	return bloomskyInfo.Storm.Rain
}

//GetRainRateMm returns rain in mm
func (bloomskyInfo BloomskyStructure) GetRainRateMm() float64 {
	return bloomskyInfo.Storm.RainRate
}

/*
	DeviceType  string  `json:"DeviceType"`
	Voltage     float64 `json:"Voltage"`
*/

// MakeNew calls bloomsky and get structurebloomsky
func MakeNew(oneConfig config.ConfigStructure) BloomskyStructure {

	var retry = 0
	var err error
	var duration = time.Minute * 5

	// get body from Rest API
	mylog.Trace.Printf("Get from Rest bloomsky API")
	myRest := rest.MakeNew()

	b := []string{oneConfig.BloomskyAccessToken}

	var m map[string][]string
	m = make(map[string][]string)
	m["Authorization"] = b

	for retry < 5 {
		err = myRest.GetWithHeaders(oneConfig.BloomskyURL, m)
		if err != nil {
			mylog.Error.Println(&bloomskyError{err, "Problem with call rest, check the URL and the secret ID in the config file"})
			retry++
			time.Sleep(duration)
		} else {
			retry = 5
		}
	}

	if err != nil {
		mylog.Error.Fatal(&bloomskyError{err, "Problem with call rest, check the URL and the secret ID in the config file"})
	}

	var bloomskyInfo []BloomskyStructure

	body := myRest.GetBody()

	mylog.Trace.Printf("Unmarshal the response")
	err = json.Unmarshal(body, &bloomskyInfo)

	if err != nil {
		mylog.Error.Fatal(&bloomskyError{err, "Problem with json to struct, problem in the struct ?"})
	}

	bloomskyInfo[0].ShowPrettyAll()

	return bloomskyInfo[0]
}
<|endoftext|>


<commit_before>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 backup_test

import (
	"errors"
	"github.com/jacobsa/comeback/backup"
	"github.com/jacobsa/comeback/backup/mock"
	"github.com/jacobsa/comeback/blob"
	"github.com/jacobsa/comeback/blob/mock"
	"github.com/jacobsa/comeback/fs"
	"github.com/jacobsa/comeback/fs/mock"
	"github.com/jacobsa/comeback/repr"
	. "github.com/jacobsa/oglematchers"
	"github.com/jacobsa/oglemock"
	. "github.com/jacobsa/ogletest"
	"testing"
)

func TestDirectoryRestorer(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func returnEntries(entries []*fs.DirectoryEntry) oglemock.Action {
	data, err := repr.Marshal(entries)
	AssertEq(nil, err)

	return oglemock.Return(data, nil)
}

func makeStrPtr(s string) *string {
	return &s
}

type DirectoryRestorerTest struct {
	blobStore    mock_blob.MockStore
	fileSystem   mock_fs.MockFileSystem
	fileRestorer mock_backup.MockFileRestorer
	wrapped      mock_backup.MockDirectoryRestorer

	dirRestorer backup.DirectoryRestorer

	score    blob.Score
	basePath string
	relPath  string

	err error
}

func init() { RegisterTestSuite(&DirectoryRestorerTest{}) }

func (t *DirectoryRestorerTest) SetUp(i *TestInfo) {
	var err error

	// Create dependencies.
	t.blobStore = mock_blob.NewMockStore(i.MockController, "blobStore")
	t.fileSystem = mock_fs.NewMockFileSystem(i.MockController, "fileSystem")
	t.fileRestorer = mock_backup.NewMockFileRestorer(i.MockController, "fileRestorer")
	t.wrapped = mock_backup.NewMockDirectoryRestorer(i.MockController, "wrapped")

	// Create restorer.
	t.dirRestorer, err = backup.NewNonRecursiveDirectoryRestorer(
		t.blobStore,
		t.fileSystem,
		t.fileRestorer,
		t.wrapped,
	)

	AssertEq(nil, err)
}

func (t *DirectoryRestorerTest) call() {
	t.err = t.dirRestorer.RestoreDirectory(t.score, t.basePath, t.relPath)
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *DirectoryRestorerTest) CallsBlobStore() {
	t.score = []byte("taco")

	// Blob store
	ExpectCall(t.blobStore, "Load")(DeepEquals(t.score)).
		WillOnce(oglemock.Return(nil, errors.New("")))

	// Call
	t.call()
}

func (t *DirectoryRestorerTest) BlobStoreReturnsError() {
	// Blob store
	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(oglemock.Return(nil, errors.New("taco")))

	// Call
	t.call()

	ExpectThat(t.err, Error(HasSubstr("Load")))
	ExpectThat(t.err, Error(HasSubstr("taco")))
}

func (t *DirectoryRestorerTest) BlobStoreReturnsJunk() {
	// Blob store
	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(oglemock.Return([]byte("taco"), nil))

	// Call
	t.call()

	ExpectThat(t.err, Error(HasSubstr("invalid")))
	ExpectThat(t.err, Error(HasSubstr("data")))
	ExpectThat(t.err, Error(HasSubstr("taco")))
}

func (t *DirectoryRestorerTest) NoEntries() {
	// Blob store
	entries := []*fs.DirectoryEntry{}

	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(returnEntries(entries))

	// Call
	t.call()

	ExpectEq(nil, t.err)
}

func (t *DirectoryRestorerTest) FileEntry_CallsLinkForHardLink() {
	t.basePath = "/foo"
	t.relPath = "bar/baz"

	// Blob store
	entries := []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Name:           "taco",
			Type:           fs.TypeFile,
			HardLinkTarget: makeStrPtr("burrito/enchilada"),
		},
	}

	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(returnEntries(entries))

	// File system
	ExpectCall(t.fileSystem, "CreateHardLink")(
		"/foo/burrito/enchilada",
		"/foo/bar/baz/taco",
	).WillOnce(oglemock.Return(errors.New("")))

	// Call
	t.call()
}

func (t *DirectoryRestorerTest) FileEntry_LinkReturnsError() {
	// Blob store
	entries := []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Type:           fs.TypeFile,
			HardLinkTarget: makeStrPtr(""),
		},
	}

	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(returnEntries(entries))

	// File system
	ExpectCall(t.fileSystem, "CreateHardLink")(Any(), Any()).
		WillOnce(oglemock.Return(errors.New("taco")))

	// Call
	t.call()

	ExpectThat(t.err, Error(HasSubstr("CreateHardLink")))
	ExpectThat(t.err, Error(HasSubstr("taco")))
}

func (t *DirectoryRestorerTest) FileEntry_LinkSucceeds() {
	// Blob store
	entries := []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Type:           fs.TypeFile,
			HardLinkTarget: makeStrPtr(""),
		},
	}

	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(returnEntries(entries))

	// File system
	ExpectCall(t.fileSystem, "CreateHardLink")(Any(), Any()).
		WillOnce(oglemock.Return(nil))

	// Call
	t.call()

	ExpectEq(nil, t.err)
}

func (t *DirectoryRestorerTest) FileEntry_CallsRestoreFile() {
	t.basePath = "/foo"
	t.relPath = "bar/baz"

	// Blob store
	entries := []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Name:           "taco",
			Type:           fs.TypeFile,
			Permissions: 0712,
			Scores: []blob.Score{
				blob.ComputeScore([]byte("burrito")),
				blob.ComputeScore([]byte("enchilada")),
			},
		},
	}

	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(returnEntries(entries))

	// File restorer
	ExpectCall(t.fileRestorer, "RestoreFile")(
		DeepEquals(entries[0].Scores),
		"/foo/bar/baz/taco",
		0712,
	).WillOnce(oglemock.Return(errors.New("")))

	// Call
	t.call()
}

func (t *DirectoryRestorerTest) FileEntry_RestoreFileReturnsError() {
	// Blob store
	entries := []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Type:           fs.TypeFile,
		},
	}

	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(returnEntries(entries))

	// File restorer
	ExpectCall(t.fileRestorer, "RestoreFile")(Any(), Any(), Any()).
		WillOnce(oglemock.Return(errors.New("taco")))

	// Call
	t.call()

	ExpectThat(t.err, Error(HasSubstr("RestoreFile")))
	ExpectThat(t.err, Error(HasSubstr("taco")))
}

func (t *DirectoryRestorerTest) DirEntry_ZeroScores() {
	// Blob store
	entries := []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Type:           fs.TypeDirectory,
			Scores: []blob.Score{},
		},
	}

	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(returnEntries(entries))

	// Call
	t.call()

	ExpectThat(t.err, Error(HasSubstr("directory")))
	ExpectThat(t.err, Error(HasSubstr("entry")))
	ExpectThat(t.err, Error(HasSubstr("exactly one")))
	ExpectThat(t.err, Error(HasSubstr("score")))
}

func (t *DirectoryRestorerTest) DirEntry_TwoScores() {
	// Blob store
	entries := []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Type:           fs.TypeDirectory,
			Scores: []blob.Score{
				blob.ComputeScore([]byte("a")),
				blob.ComputeScore([]byte("b")),
			},
		},
	}

	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(returnEntries(entries))

	// Call
	t.call()

	ExpectThat(t.err, Error(HasSubstr("directory")))
	ExpectThat(t.err, Error(HasSubstr("entry")))
	ExpectThat(t.err, Error(HasSubstr("exactly one")))
	ExpectThat(t.err, Error(HasSubstr("score")))
}

func (t *DirectoryRestorerTest) DirEntry_CallsMkdir() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) DirEntry_MkdirReturnsError() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) DirEntry_CallsWrapped() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) DirEntry_WrappedReturnsError() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) SymlinkEntry_CallsSymlink() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) SymlinkEntry_SymlinkReturnsError() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) PipeEntry_CallsCreate() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) PipeEntry_CreateReturnsError() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) BlockDevEntry_CallsCreate() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) BlockDevEntry_CreateReturnsError() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) CharDevEntry_CallsCreate() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) CharDevEntry_CreateReturnsError() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) CallsChown() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) ChownReturnsErrorForOneEntry() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) CallsSetModTime() {
	ExpectEq("TODO", "")
	// NOTE: Not for devices (see restore.go)
}

func (t *DirectoryRestorerTest) SetModTimeReturnsErrorForOneEntry() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) EverythingSucceeds() {
	ExpectEq("TODO", "")
}
<commit_msg>DirEntry_CallsWrapped<commit_after>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 backup_test

import (
	"errors"
	"github.com/jacobsa/comeback/backup"
	"github.com/jacobsa/comeback/backup/mock"
	"github.com/jacobsa/comeback/blob"
	"github.com/jacobsa/comeback/blob/mock"
	"github.com/jacobsa/comeback/fs"
	"github.com/jacobsa/comeback/fs/mock"
	"github.com/jacobsa/comeback/repr"
	. "github.com/jacobsa/oglematchers"
	"github.com/jacobsa/oglemock"
	. "github.com/jacobsa/ogletest"
	"testing"
)

func TestDirectoryRestorer(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func returnEntries(entries []*fs.DirectoryEntry) oglemock.Action {
	data, err := repr.Marshal(entries)
	AssertEq(nil, err)

	return oglemock.Return(data, nil)
}

func makeStrPtr(s string) *string {
	return &s
}

type DirectoryRestorerTest struct {
	blobStore    mock_blob.MockStore
	fileSystem   mock_fs.MockFileSystem
	fileRestorer mock_backup.MockFileRestorer
	wrapped      mock_backup.MockDirectoryRestorer

	dirRestorer backup.DirectoryRestorer

	score    blob.Score
	basePath string
	relPath  string

	err error
}

func init() { RegisterTestSuite(&DirectoryRestorerTest{}) }

func (t *DirectoryRestorerTest) SetUp(i *TestInfo) {
	var err error

	// Create dependencies.
	t.blobStore = mock_blob.NewMockStore(i.MockController, "blobStore")
	t.fileSystem = mock_fs.NewMockFileSystem(i.MockController, "fileSystem")
	t.fileRestorer = mock_backup.NewMockFileRestorer(i.MockController, "fileRestorer")
	t.wrapped = mock_backup.NewMockDirectoryRestorer(i.MockController, "wrapped")

	// Create restorer.
	t.dirRestorer, err = backup.NewNonRecursiveDirectoryRestorer(
		t.blobStore,
		t.fileSystem,
		t.fileRestorer,
		t.wrapped,
	)

	AssertEq(nil, err)
}

func (t *DirectoryRestorerTest) call() {
	t.err = t.dirRestorer.RestoreDirectory(t.score, t.basePath, t.relPath)
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *DirectoryRestorerTest) CallsBlobStore() {
	t.score = []byte("taco")

	// Blob store
	ExpectCall(t.blobStore, "Load")(DeepEquals(t.score)).
		WillOnce(oglemock.Return(nil, errors.New("")))

	// Call
	t.call()
}

func (t *DirectoryRestorerTest) BlobStoreReturnsError() {
	// Blob store
	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(oglemock.Return(nil, errors.New("taco")))

	// Call
	t.call()

	ExpectThat(t.err, Error(HasSubstr("Load")))
	ExpectThat(t.err, Error(HasSubstr("taco")))
}

func (t *DirectoryRestorerTest) BlobStoreReturnsJunk() {
	// Blob store
	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(oglemock.Return([]byte("taco"), nil))

	// Call
	t.call()

	ExpectThat(t.err, Error(HasSubstr("invalid")))
	ExpectThat(t.err, Error(HasSubstr("data")))
	ExpectThat(t.err, Error(HasSubstr("taco")))
}

func (t *DirectoryRestorerTest) NoEntries() {
	// Blob store
	entries := []*fs.DirectoryEntry{}

	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(returnEntries(entries))

	// Call
	t.call()

	ExpectEq(nil, t.err)
}

func (t *DirectoryRestorerTest) FileEntry_CallsLinkForHardLink() {
	t.basePath = "/foo"
	t.relPath = "bar/baz"

	// Blob store
	entries := []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Name:           "taco",
			Type:           fs.TypeFile,
			HardLinkTarget: makeStrPtr("burrito/enchilada"),
		},
	}

	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(returnEntries(entries))

	// File system
	ExpectCall(t.fileSystem, "CreateHardLink")(
		"/foo/burrito/enchilada",
		"/foo/bar/baz/taco",
	).WillOnce(oglemock.Return(errors.New("")))

	// Call
	t.call()
}

func (t *DirectoryRestorerTest) FileEntry_LinkReturnsError() {
	// Blob store
	entries := []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Type:           fs.TypeFile,
			HardLinkTarget: makeStrPtr(""),
		},
	}

	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(returnEntries(entries))

	// File system
	ExpectCall(t.fileSystem, "CreateHardLink")(Any(), Any()).
		WillOnce(oglemock.Return(errors.New("taco")))

	// Call
	t.call()

	ExpectThat(t.err, Error(HasSubstr("CreateHardLink")))
	ExpectThat(t.err, Error(HasSubstr("taco")))
}

func (t *DirectoryRestorerTest) FileEntry_LinkSucceeds() {
	// Blob store
	entries := []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Type:           fs.TypeFile,
			HardLinkTarget: makeStrPtr(""),
		},
	}

	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(returnEntries(entries))

	// File system
	ExpectCall(t.fileSystem, "CreateHardLink")(Any(), Any()).
		WillOnce(oglemock.Return(nil))

	// Call
	t.call()

	ExpectEq(nil, t.err)
}

func (t *DirectoryRestorerTest) FileEntry_CallsRestoreFile() {
	t.basePath = "/foo"
	t.relPath = "bar/baz"

	// Blob store
	entries := []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Name:           "taco",
			Type:           fs.TypeFile,
			Permissions: 0712,
			Scores: []blob.Score{
				blob.ComputeScore([]byte("burrito")),
				blob.ComputeScore([]byte("enchilada")),
			},
		},
	}

	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(returnEntries(entries))

	// File restorer
	ExpectCall(t.fileRestorer, "RestoreFile")(
		DeepEquals(entries[0].Scores),
		"/foo/bar/baz/taco",
		0712,
	).WillOnce(oglemock.Return(errors.New("")))

	// Call
	t.call()
}

func (t *DirectoryRestorerTest) FileEntry_RestoreFileReturnsError() {
	// Blob store
	entries := []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Type:           fs.TypeFile,
		},
	}

	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(returnEntries(entries))

	// File restorer
	ExpectCall(t.fileRestorer, "RestoreFile")(Any(), Any(), Any()).
		WillOnce(oglemock.Return(errors.New("taco")))

	// Call
	t.call()

	ExpectThat(t.err, Error(HasSubstr("RestoreFile")))
	ExpectThat(t.err, Error(HasSubstr("taco")))
}

func (t *DirectoryRestorerTest) DirEntry_ZeroScores() {
	// Blob store
	entries := []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Type:           fs.TypeDirectory,
			Scores: []blob.Score{},
		},
	}

	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(returnEntries(entries))

	// Call
	t.call()

	ExpectThat(t.err, Error(HasSubstr("directory")))
	ExpectThat(t.err, Error(HasSubstr("entry")))
	ExpectThat(t.err, Error(HasSubstr("exactly one")))
	ExpectThat(t.err, Error(HasSubstr("score")))
}

func (t *DirectoryRestorerTest) DirEntry_TwoScores() {
	// Blob store
	entries := []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Type:           fs.TypeDirectory,
			Scores: []blob.Score{
				blob.ComputeScore([]byte("a")),
				blob.ComputeScore([]byte("b")),
			},
		},
	}

	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(returnEntries(entries))

	// Call
	t.call()

	ExpectThat(t.err, Error(HasSubstr("directory")))
	ExpectThat(t.err, Error(HasSubstr("entry")))
	ExpectThat(t.err, Error(HasSubstr("exactly one")))
	ExpectThat(t.err, Error(HasSubstr("score")))
}

func (t *DirectoryRestorerTest) DirEntry_CallsMkdir() {
	t.basePath = "/foo"
	t.relPath = "bar/baz"

	// Blob store
	entries := []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Name: "taco",
			Type:           fs.TypeDirectory,
			Permissions: 0712,
			Scores: []blob.Score{blob.ComputeScore([]byte(""))},
		},
	}

	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(returnEntries(entries))

	// File system
	ExpectCall(t.fileSystem, "Mkdir")("/foo/bar/baz/taco", 0712).
		WillOnce(oglemock.Return(errors.New("")))

	// Call
	t.call()
}

func (t *DirectoryRestorerTest) DirEntry_MkdirReturnsError() {
	// Blob store
	entries := []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Type:           fs.TypeDirectory,
			Scores: []blob.Score{blob.ComputeScore([]byte(""))},
		},
	}

	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(returnEntries(entries))

	// File system
	ExpectCall(t.fileSystem, "Mkdir")(Any(), Any()).
		WillOnce(oglemock.Return(errors.New("taco")))

	// Call
	t.call()

	ExpectThat(t.err, Error(HasSubstr("Mkdir")))
	ExpectThat(t.err, Error(HasSubstr("taco")))
}

func (t *DirectoryRestorerTest) DirEntry_CallsWrapped() {
	t.basePath = "/foo"
	t.relPath = "bar/baz"

	// Blob store
	entries := []*fs.DirectoryEntry{
		&fs.DirectoryEntry{
			Name: "taco",
			Type:           fs.TypeDirectory,
			Scores: []blob.Score{blob.ComputeScore([]byte("burrito"))},
		},
	}

	ExpectCall(t.blobStore, "Load")(Any()).
		WillOnce(returnEntries(entries))

	// File system
	ExpectCall(t.fileSystem, "Mkdir")(Any(), Any()).
		WillOnce(oglemock.Return(nil))

	// Wrapped
	ExpectCall(t.wrapped, "RestoreDirectory")(
		DeepEquals(blob.ComputeScore([]byte("burrito"))),
		"/foo",
		"bar/baz/taco",
	).WillOnce(oglemock.Return(errors.New("")))

	// Call
	t.call()
}

func (t *DirectoryRestorerTest) DirEntry_WrappedReturnsError() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) SymlinkEntry_CallsSymlink() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) SymlinkEntry_SymlinkReturnsError() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) PipeEntry_CallsCreate() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) PipeEntry_CreateReturnsError() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) BlockDevEntry_CallsCreate() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) BlockDevEntry_CreateReturnsError() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) CharDevEntry_CallsCreate() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) CharDevEntry_CreateReturnsError() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) CallsChown() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) ChownReturnsErrorForOneEntry() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) CallsSetModTime() {
	ExpectEq("TODO", "")
	// NOTE: Not for devices (see restore.go)
}

func (t *DirectoryRestorerTest) SetModTimeReturnsErrorForOneEntry() {
	ExpectEq("TODO", "")
}

func (t *DirectoryRestorerTest) EverythingSucceeds() {
	ExpectEq("TODO", "")
}
<|endoftext|>


<commit_before>package helper

import (
	"reflect"
	"testing"

	"github.com/OJ/gobuster/v3/libgobuster"
)

func TestParseExtensions(t *testing.T) {
	t.Parallel()

	var tt = []struct {
		testName           string
		extensions         string
		expectedExtensions libgobuster.StringSet
		expectedError      string
	}{
		{"Valid extensions", "php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
		{"Spaces", "php, asp , txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
		{"Double extensions", "php,asp,txt,php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
		{"Leading dot", ".php,asp,.txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
		{"Empty string", "", libgobuster.NewStringSet(), "invalid extension string provided"},
	}

	for _, x := range tt {
		t.Run(x.testName, func(t *testing.T) {
			ret, err := ParseExtensions(x.extensions)
			if x.expectedError != "" {
				if err.Error() != x.expectedError {
					t.Fatalf("Expected error %q but got %q", x.expectedError, err.Error())
				}
			} else if !reflect.DeepEqual(x.expectedExtensions, ret) {
				t.Fatalf("Expected %v but got %v", x.expectedExtensions, ret)
			}
		})
	}
}

func TestParseStatusCodes(t *testing.T) {
	t.Parallel()

	var tt = []struct {
		testName      string
		stringCodes   string
		expectedCodes libgobuster.IntSet
		expectedError string
	}{
		{"Valid codes", "200,100,202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
		{"Spaces", "200, 100 , 202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
		{"Double codes", "200, 100, 202, 100", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
		{"Invalid code", "200,AAA", libgobuster.NewIntSet(), "invalid status code given: AAA"},
		{"Invalid integer", "2000000000000000000000000000000", libgobuster.NewIntSet(), "invalid status code given: 2000000000000000000000000000000"},
		{"Empty string", "", libgobuster.NewIntSet(), "invalid status code string provided"},
	}

	for _, x := range tt {
		t.Run(x.testName, func(t *testing.T) {
			ret, err := ParseStatusCodes(x.stringCodes)
			if x.expectedError != "" {
				if err.Error() != x.expectedError {
					t.Fatalf("Expected error %q but got %q", x.expectedError, err.Error())
				}
			} else if !reflect.DeepEqual(x.expectedCodes, ret) {
				t.Fatalf("Expected %v but got %v", x.expectedCodes, ret)
			}
		})
	}
}
<commit_msg>more benchmarks<commit_after>package helper

import (
	"reflect"
	"testing"

	"github.com/OJ/gobuster/v3/libgobuster"
)

func TestParseExtensions(t *testing.T) {
	t.Parallel()

	var tt = []struct {
		testName           string
		extensions         string
		expectedExtensions libgobuster.StringSet
		expectedError      string
	}{
		{"Valid extensions", "php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
		{"Spaces", "php, asp , txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
		{"Double extensions", "php,asp,txt,php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
		{"Leading dot", ".php,asp,.txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
		{"Empty string", "", libgobuster.NewStringSet(), "invalid extension string provided"},
	}

	for _, x := range tt {
		t.Run(x.testName, func(t *testing.T) {
			ret, err := ParseExtensions(x.extensions)
			if x.expectedError != "" {
				if err.Error() != x.expectedError {
					t.Fatalf("Expected error %q but got %q", x.expectedError, err.Error())
				}
			} else if !reflect.DeepEqual(x.expectedExtensions, ret) {
				t.Fatalf("Expected %v but got %v", x.expectedExtensions, ret)
			}
		})
	}
}

func TestParseStatusCodes(t *testing.T) {
	t.Parallel()

	var tt = []struct {
		testName      string
		stringCodes   string
		expectedCodes libgobuster.IntSet
		expectedError string
	}{
		{"Valid codes", "200,100,202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
		{"Spaces", "200, 100 , 202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
		{"Double codes", "200, 100, 202, 100", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
		{"Invalid code", "200,AAA", libgobuster.NewIntSet(), "invalid status code given: AAA"},
		{"Invalid integer", "2000000000000000000000000000000", libgobuster.NewIntSet(), "invalid status code given: 2000000000000000000000000000000"},
		{"Empty string", "", libgobuster.NewIntSet(), "invalid status code string provided"},
	}

	for _, x := range tt {
		t.Run(x.testName, func(t *testing.T) {
			ret, err := ParseStatusCodes(x.stringCodes)
			if x.expectedError != "" {
				if err.Error() != x.expectedError {
					t.Fatalf("Expected error %q but got %q", x.expectedError, err.Error())
				}
			} else if !reflect.DeepEqual(x.expectedCodes, ret) {
				t.Fatalf("Expected %v but got %v", x.expectedCodes, ret)
			}
		})
	}
}

func BenchmarkParseExtensions(b *testing.B) {
	var tt = []struct {
		testName           string
		extensions         string
		expectedExtensions libgobuster.StringSet
		expectedError      string
	}{
		{"Valid extensions", "php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
		{"Spaces", "php, asp , txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
		{"Double extensions", "php,asp,txt,php,asp,txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
		{"Leading dot", ".php,asp,.txt", libgobuster.StringSet{Set: map[string]bool{"php": true, "asp": true, "txt": true}}, ""},
		{"Empty string", "", libgobuster.NewStringSet(), "invalid extension string provided"},
	}

	for _, x := range tt {
		b.Run(x.testName, func(b2 *testing.B) {
			for y := 0; y < b2.N; y++ {
				_, _ = ParseExtensions(x.extensions)
			}
		})
	}
}

func BenchmarkParseStatusCodes(b *testing.B) {
	var tt = []struct {
		testName      string
		stringCodes   string
		expectedCodes libgobuster.IntSet
		expectedError string
	}{
		{"Valid codes", "200,100,202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
		{"Spaces", "200, 100 , 202", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
		{"Double codes", "200, 100, 202, 100", libgobuster.IntSet{Set: map[int]bool{100: true, 200: true, 202: true}}, ""},
		{"Invalid code", "200,AAA", libgobuster.NewIntSet(), "invalid status code given: AAA"},
		{"Invalid integer", "2000000000000000000000000000000", libgobuster.NewIntSet(), "invalid status code given: 2000000000000000000000000000000"},
		{"Empty string", "", libgobuster.NewIntSet(), "invalid status code string provided"},
	}

	for _, x := range tt {
		b.Run(x.testName, func(b2 *testing.B) {
			for y := 0; y < b2.N; y++ {
				_, _ = ParseStatusCodes(x.stringCodes)
			}
		})
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"time"

	"github.com/yamnikov-oleg/avamon-bot/monitor"
)

func main() {
	timeout := flag.Duration("timeout", 3*time.Second, "Timeout for network request")

	flag.Parse()

	poller := monitor.NewPoller()
	poller.Timeout = *timeout

	urls := flag.Args()
	for _, url := range urls {
		fmt.Printf("Requesting %q\n", url)
		status := poller.PollService(url)
		fmt.Println(status)
	}
}
<commit_msg>:wrench: Make avamon-poll command print expanded statuses<commit_after>package main

import (
	"flag"
	"fmt"
	"time"

	"github.com/yamnikov-oleg/avamon-bot/monitor"
)

func main() {
	timeout := flag.Duration("timeout", 3*time.Second, "Timeout for network request")

	flag.Parse()

	poller := monitor.NewPoller()
	poller.Timeout = *timeout

	urls := flag.Args()
	for _, url := range urls {
		fmt.Printf("Requesting %q\n", url)
		status := poller.PollService(url)
		fmt.Println(status.ExpandedString())
	}
}
<|endoftext|>


<commit_before><commit_msg>Usage of go.uuid package<commit_after><|endoftext|>


<commit_before>// Copyright 2021 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use 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 tcell

// Screen represents the physical (or emulated) screen.
// This can be a terminal window or a physical console.  Platforms implement
// this differently.
type Screen interface {
	// Init initializes the screen for use.
	Init() error

	// Fini finalizes the screen also releasing resources.
	Fini()

	// Clear erases the screen.  The contents of any screen buffers
	// will also be cleared.  This has the logical effect of
	// filling the screen with spaces, using the global default style.
	Clear()

	// Fill fills the screen with the given character and style.
	Fill(rune, Style)

	// SetCell is an older API, and will be removed.  Please use
	// SetContent instead; SetCell is implemented in terms of SetContent.
	SetCell(x int, y int, style Style, ch ...rune)

	// GetContent returns the contents at the given location.  If the
	// coordinates are out of range, then the values will be 0, nil,
	// StyleDefault.  Note that the contents returned are logical contents
	// and may not actually be what is displayed, but rather are what will
	// be displayed if Show() or Sync() is called.  The width is the width
	// in screen cells; most often this will be 1, but some East Asian
	// characters require two cells.
	GetContent(x, y int) (mainc rune, combc []rune, style Style, width int)

	// SetContent sets the contents of the given cell location.  If
	// the coordinates are out of range, then the operation is ignored.
	//
	// The first rune is the primary non-zero width rune.  The array
	// that follows is a possible list of combining characters to append,
	// and will usually be nil (no combining characters.)
	//
	// The results are not displayd until Show() or Sync() is called.
	//
	// Note that wide (East Asian full width) runes occupy two cells,
	// and attempts to place character at next cell to the right will have
	// undefined effects.  Wide runes that are printed in the
	// last column will be replaced with a single width space on output.
	SetContent(x int, y int, mainc rune, combc []rune, style Style)

	// SetStyle sets the default style to use when clearing the screen
	// or when StyleDefault is specified.  If it is also StyleDefault,
	// then whatever system/terminal default is relevant will be used.
	SetStyle(style Style)

	// ShowCursor is used to display the cursor at a given location.
	// If the coordinates -1, -1 are given or are otherwise outside the
	// dimensions of the screen, the cursor will be hidden.
	ShowCursor(x int, y int)

	// HideCursor is used to hide the cursor.  Its an alias for
	// ShowCursor(-1, -1).
	HideCursor()

	// Size returns the screen size as width, height.  This changes in
	// response to a call to Clear or Flush.
	Size() (int, int)

	// PollEvent waits for events to arrive.  Main application loops
	// must spin on this to prevent the application from stalling.
	// Furthermore, this will return nil if the Screen is finalized.
	PollEvent() Event

	// PostEvent tries to post an event into the event stream.  This
	// can fail if the event queue is full.  In that case, the event
	// is dropped, and ErrEventQFull is returned.
	PostEvent(ev Event) error

	// PostEventWait is like PostEvent, but if the queue is full, it
	// blocks until there is space in the queue, making delivery
	// reliable.  However, it is VERY important that this function
	// never be called from within whatever event loop is polling
	// with PollEvent(), otherwise a deadlock may arise.
	//
	// For this reason, when using this function, the use of a
	// Goroutine is recommended to ensure no deadlock can occur.
	PostEventWait(ev Event)

	// EnableMouse enables the mouse.  (If your terminal supports it.)
	// If no flags are specified, then all events are reported, if the
	// terminal supports them.
	EnableMouse(...MouseFlags)

	// DisableMouse disables the mouse.
	DisableMouse()

	// EnablePaste enables bracketed paste mode, if supported.
	EnablePaste()

	// DisablePaste() disables bracketed paste mode.
	DisablePaste()

	// HasMouse returns true if the terminal (apparently) supports a
	// mouse.  Note that the a return value of true doesn't guarantee that
	// a mouse/pointing device is present; a false return definitely
	// indicates no mouse support is available.
	HasMouse() bool

	// Colors returns the number of colors.  All colors are assumed to
	// use the ANSI color map.  If a terminal is monochrome, it will
	// return 0.
	Colors() int

	// Show makes all the content changes made using SetContent() visible
	// on the display.
	//
	// It does so in the most efficient and least visually disruptive
	// manner possible.
	Show()

	// Sync works like Show(), but it updates every visible cell on the
	// physical display, assuming that it is not synchronized with any
	// internal model.  This may be both expensive and visually jarring,
	// so it should only be used when believed to actually be necessary.
	//
	// Typically this is called as a result of a user-requested redraw
	// (e.g. to clear up on screen corruption caused by some other program),
	// or during a resize event.
	Sync()

	// CharacterSet returns information about the character set.
	// This isn't the full locale, but it does give us the input/output
	// character set.  Note that this is just for diagnostic purposes,
	// we normally translate input/output to/from UTF-8, regardless of
	// what the user's environment is.
	CharacterSet() string

	// RegisterRuneFallback adds a fallback for runes that are not
	// part of the character set -- for example one coudld register
	// o as a fallback for ø.  This should be done cautiously for
	// characters that might be displayed ordinarily in language
	// specific text -- characters that could change the meaning of
	// of written text would be dangerous.  The intention here is to
	// facilitate fallback characters in pseudo-graphical applications.
	//
	// If the terminal has fallbacks already in place via an alternate
	// character set, those are used in preference.  Also, standard
	// fallbacks for graphical characters in the ACSC terminfo string
	// are registered implicitly.

	// The display string should be the same width as original rune.
	// This makes it possible to register two character replacements
	// for full width East Asian characters, for example.
	//
	// It is recommended that replacement strings consist only of
	// 7-bit ASCII, since other characters may not display everywhere.
	RegisterRuneFallback(r rune, subst string)

	// UnregisterRuneFallback unmaps a replacement.  It will unmap
	// the implicit ASCII replacements for alternate characters as well.
	// When an unmapped char needs to be displayed, but no suitable
	// glyph is available, '?' is emitted instead.  It is not possible
	// to "disable" the use of alternate characters that are supported
	// by your terminal except by changing the terminal database.
	UnregisterRuneFallback(r rune)

	// CanDisplay returns true if the given rune can be displayed on
	// this screen.  Note that this is a best guess effort -- whether
	// your fonts support the character or not may be questionable.
	// Mostly this is for folks who work outside of Unicode.
	//
	// If checkFallbacks is true, then if any (possibly imperfect)
	// fallbacks are registered, this will return true.  This will
	// also return true if the terminal can replace the glyph with
	// one that is visually indistinguishable from the one requested.
	CanDisplay(r rune, checkFallbacks bool) bool

	// Resize does nothing, since its generally not possible to
	// ask a screen to resize, but it allows the Screen to implement
	// the View interface.
	Resize(int, int, int, int)

	// HasKey returns true if the keyboard is believed to have the
	// key.  In some cases a keyboard may have keys with this name
	// but no support for them, while in others a key may be reported
	// as supported but not actually be usable (such as some emulators
	// that hijack certain keys).  Its best not to depend to strictly
	// on this function, but it can be used for hinting when building
	// menus, displayed hot-keys, etc.  Note that KeyRune (literal
	// runes) is always true.
	HasKey(Key) bool

	// Beep attempts to sound an OS-dependent audible alert and returns an error
	// when unsuccessful.
	Beep() error
}

// NewScreen returns a default Screen suitable for the user's terminal
// environment.
func NewScreen() (Screen, error) {
	// Windows is happier if we try for a console screen first.
	if s, _ := NewConsoleScreen(); s != nil {
		return s, nil
	} else if s, e := NewTerminfoScreen(); s != nil {
		return s, nil
	} else {
		return nil, e
	}
}

// MouseFlags are options to modify the handling of mouse events.
// Actual events can be or'd together.
type MouseFlags int

const (
	MouseButtonEvents = MouseFlags(1) // Click events only
	MouseDragEvents   = MouseFlags(2) // Click-drag events (includes button events)
	MouseMotionEvents = MouseFlags(4) // All mouse events (includes click and drag events)
)
<commit_msg>Mark PostEventWait deprecated.<commit_after>// Copyright 2021 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use 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 tcell

// Screen represents the physical (or emulated) screen.
// This can be a terminal window or a physical console.  Platforms implement
// this differently.
type Screen interface {
	// Init initializes the screen for use.
	Init() error

	// Fini finalizes the screen also releasing resources.
	Fini()

	// Clear erases the screen.  The contents of any screen buffers
	// will also be cleared.  This has the logical effect of
	// filling the screen with spaces, using the global default style.
	Clear()

	// Fill fills the screen with the given character and style.
	Fill(rune, Style)

	// SetCell is an older API, and will be removed.  Please use
	// SetContent instead; SetCell is implemented in terms of SetContent.
	SetCell(x int, y int, style Style, ch ...rune)

	// GetContent returns the contents at the given location.  If the
	// coordinates are out of range, then the values will be 0, nil,
	// StyleDefault.  Note that the contents returned are logical contents
	// and may not actually be what is displayed, but rather are what will
	// be displayed if Show() or Sync() is called.  The width is the width
	// in screen cells; most often this will be 1, but some East Asian
	// characters require two cells.
	GetContent(x, y int) (mainc rune, combc []rune, style Style, width int)

	// SetContent sets the contents of the given cell location.  If
	// the coordinates are out of range, then the operation is ignored.
	//
	// The first rune is the primary non-zero width rune.  The array
	// that follows is a possible list of combining characters to append,
	// and will usually be nil (no combining characters.)
	//
	// The results are not displayd until Show() or Sync() is called.
	//
	// Note that wide (East Asian full width) runes occupy two cells,
	// and attempts to place character at next cell to the right will have
	// undefined effects.  Wide runes that are printed in the
	// last column will be replaced with a single width space on output.
	SetContent(x int, y int, mainc rune, combc []rune, style Style)

	// SetStyle sets the default style to use when clearing the screen
	// or when StyleDefault is specified.  If it is also StyleDefault,
	// then whatever system/terminal default is relevant will be used.
	SetStyle(style Style)

	// ShowCursor is used to display the cursor at a given location.
	// If the coordinates -1, -1 are given or are otherwise outside the
	// dimensions of the screen, the cursor will be hidden.
	ShowCursor(x int, y int)

	// HideCursor is used to hide the cursor.  Its an alias for
	// ShowCursor(-1, -1).
	HideCursor()

	// Size returns the screen size as width, height.  This changes in
	// response to a call to Clear or Flush.
	Size() (int, int)

	// PollEvent waits for events to arrive.  Main application loops
	// must spin on this to prevent the application from stalling.
	// Furthermore, this will return nil if the Screen is finalized.
	PollEvent() Event

	// PostEvent tries to post an event into the event stream.  This
	// can fail if the event queue is full.  In that case, the event
	// is dropped, and ErrEventQFull is returned.
	PostEvent(ev Event) error

	// Deprecated: PostEventWait is unsafe, and will be removed
	// in the future.
	//
	// PostEventWait is like PostEvent, but if the queue is full, it
	// blocks until there is space in the queue, making delivery
	// reliable.  However, it is VERY important that this function
	// never be called from within whatever event loop is polling
	// with PollEvent(), otherwise a deadlock may arise.
	//
	// For this reason, when using this function, the use of a
	// Goroutine is recommended to ensure no deadlock can occur.
	PostEventWait(ev Event)

	// EnableMouse enables the mouse.  (If your terminal supports it.)
	// If no flags are specified, then all events are reported, if the
	// terminal supports them.
	EnableMouse(...MouseFlags)

	// DisableMouse disables the mouse.
	DisableMouse()

	// EnablePaste enables bracketed paste mode, if supported.
	EnablePaste()

	// DisablePaste() disables bracketed paste mode.
	DisablePaste()

	// HasMouse returns true if the terminal (apparently) supports a
	// mouse.  Note that the a return value of true doesn't guarantee that
	// a mouse/pointing device is present; a false return definitely
	// indicates no mouse support is available.
	HasMouse() bool

	// Colors returns the number of colors.  All colors are assumed to
	// use the ANSI color map.  If a terminal is monochrome, it will
	// return 0.
	Colors() int

	// Show makes all the content changes made using SetContent() visible
	// on the display.
	//
	// It does so in the most efficient and least visually disruptive
	// manner possible.
	Show()

	// Sync works like Show(), but it updates every visible cell on the
	// physical display, assuming that it is not synchronized with any
	// internal model.  This may be both expensive and visually jarring,
	// so it should only be used when believed to actually be necessary.
	//
	// Typically this is called as a result of a user-requested redraw
	// (e.g. to clear up on screen corruption caused by some other program),
	// or during a resize event.
	Sync()

	// CharacterSet returns information about the character set.
	// This isn't the full locale, but it does give us the input/output
	// character set.  Note that this is just for diagnostic purposes,
	// we normally translate input/output to/from UTF-8, regardless of
	// what the user's environment is.
	CharacterSet() string

	// RegisterRuneFallback adds a fallback for runes that are not
	// part of the character set -- for example one coudld register
	// o as a fallback for ø.  This should be done cautiously for
	// characters that might be displayed ordinarily in language
	// specific text -- characters that could change the meaning of
	// of written text would be dangerous.  The intention here is to
	// facilitate fallback characters in pseudo-graphical applications.
	//
	// If the terminal has fallbacks already in place via an alternate
	// character set, those are used in preference.  Also, standard
	// fallbacks for graphical characters in the ACSC terminfo string
	// are registered implicitly.

	// The display string should be the same width as original rune.
	// This makes it possible to register two character replacements
	// for full width East Asian characters, for example.
	//
	// It is recommended that replacement strings consist only of
	// 7-bit ASCII, since other characters may not display everywhere.
	RegisterRuneFallback(r rune, subst string)

	// UnregisterRuneFallback unmaps a replacement.  It will unmap
	// the implicit ASCII replacements for alternate characters as well.
	// When an unmapped char needs to be displayed, but no suitable
	// glyph is available, '?' is emitted instead.  It is not possible
	// to "disable" the use of alternate characters that are supported
	// by your terminal except by changing the terminal database.
	UnregisterRuneFallback(r rune)

	// CanDisplay returns true if the given rune can be displayed on
	// this screen.  Note that this is a best guess effort -- whether
	// your fonts support the character or not may be questionable.
	// Mostly this is for folks who work outside of Unicode.
	//
	// If checkFallbacks is true, then if any (possibly imperfect)
	// fallbacks are registered, this will return true.  This will
	// also return true if the terminal can replace the glyph with
	// one that is visually indistinguishable from the one requested.
	CanDisplay(r rune, checkFallbacks bool) bool

	// Resize does nothing, since its generally not possible to
	// ask a screen to resize, but it allows the Screen to implement
	// the View interface.
	Resize(int, int, int, int)

	// HasKey returns true if the keyboard is believed to have the
	// key.  In some cases a keyboard may have keys with this name
	// but no support for them, while in others a key may be reported
	// as supported but not actually be usable (such as some emulators
	// that hijack certain keys).  Its best not to depend to strictly
	// on this function, but it can be used for hinting when building
	// menus, displayed hot-keys, etc.  Note that KeyRune (literal
	// runes) is always true.
	HasKey(Key) bool

	// Beep attempts to sound an OS-dependent audible alert and returns an error
	// when unsuccessful.
	Beep() error
}

// NewScreen returns a default Screen suitable for the user's terminal
// environment.
func NewScreen() (Screen, error) {
	// Windows is happier if we try for a console screen first.
	if s, _ := NewConsoleScreen(); s != nil {
		return s, nil
	} else if s, e := NewTerminfoScreen(); s != nil {
		return s, nil
	} else {
		return nil, e
	}
}

// MouseFlags are options to modify the handling of mouse events.
// Actual events can be or'd together.
type MouseFlags int

const (
	MouseButtonEvents = MouseFlags(1) // Click events only
	MouseDragEvents   = MouseFlags(2) // Click-drag events (includes button events)
	MouseMotionEvents = MouseFlags(4) // All mouse events (includes click and drag events)
)
<|endoftext|>


<commit_before>// Copyright 2010 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 inotify implements a wrapper for the Linux inotify system.

Example:
    watcher, err := inotify.NewWatcher()
    if err != nil {
        log.Fatal(err)
    }
    err = watcher.Watch("/tmp")
    if err != nil {
        log.Fatal(err)
    }
    for {
        select {
        case ev := <-watcher.Event:
            log.Println("event:", ev)
        case err := <-watcher.Error:
            log.Println("error:", err)
        }
    }

*/
package inotify // import "golang.org/x/exp/inotify"

import (
	"errors"
	"fmt"
	"os"
	"strings"
	"sync"
	"syscall"
	"unsafe"
)

type Event struct {
	Mask   uint32 // Mask of events
	Cookie uint32 // Unique cookie associating related events (for rename(2))
	Name   string // File name (optional)
}

type watch struct {
	wd    uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
	flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
}

type Watcher struct {
	mu       sync.Mutex
	fd       int               // File descriptor (as returned by the inotify_init() syscall)
	watches  map[string]*watch // Map of inotify watches (key: path)
	paths    map[int]string    // Map of watched paths (key: watch descriptor)
	Error    chan error        // Errors are sent on this channel
	Event    chan *Event       // Events are returned on this channel
	done     chan bool         // Channel for sending a "quit message" to the reader goroutine
	isClosed bool              // Set to true when Close() is first called
}

// NewWatcher creates and returns a new inotify instance using inotify_init(2)
func NewWatcher() (*Watcher, error) {
	fd, errno := syscall.InotifyInit()
	if fd == -1 {
		return nil, os.NewSyscallError("inotify_init", errno)
	}
	w := &Watcher{
		fd:      fd,
		watches: make(map[string]*watch),
		paths:   make(map[int]string),
		Event:   make(chan *Event),
		Error:   make(chan error),
		done:    make(chan bool, 1),
	}

	go w.readEvents()
	return w, nil
}

// Close closes an inotify watcher instance
// It sends a message to the reader goroutine to quit and removes all watches
// associated with the inotify instance
func (w *Watcher) Close() error {
	if w.isClosed {
		return nil
	}
	w.isClosed = true

	// Send "quit" message to the reader goroutine
	w.done <- true
	for path := range w.watches {
		w.RemoveWatch(path)
	}

	return nil
}

// AddWatch adds path to the watched file set.
// The flags are interpreted as described in inotify_add_watch(2).
func (w *Watcher) AddWatch(path string, flags uint32) error {
	if w.isClosed {
		return errors.New("inotify instance already closed")
	}

	watchEntry, found := w.watches[path]
	if found {
		watchEntry.flags |= flags
		flags |= syscall.IN_MASK_ADD
	}

	w.mu.Lock() // synchronize with readEvents goroutine

	wd, err := syscall.InotifyAddWatch(w.fd, path, flags)
	if err != nil {
		w.mu.Unlock()
		return &os.PathError{
			Op:   "inotify_add_watch",
			Path: path,
			Err:  err,
		}
	}

	if !found {
		w.watches[path] = &watch{wd: uint32(wd), flags: flags}
		w.paths[wd] = path
	}
	w.mu.Unlock()
	return nil
}

// Watch adds path to the watched file set, watching all events.
func (w *Watcher) Watch(path string) error {
	return w.AddWatch(path, IN_ALL_EVENTS)
}

// RemoveWatch removes path from the watched file set.
func (w *Watcher) RemoveWatch(path string) error {
	watch, ok := w.watches[path]
	if !ok {
		return errors.New(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path))
	}
	success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
	if success == -1 {
		return os.NewSyscallError("inotify_rm_watch", errno)
	}
	delete(w.watches, path)
	return nil
}

// readEvents reads from the inotify file descriptor, converts the
// received events into Event objects and sends them via the Event channel
func (w *Watcher) readEvents() {
	var buf [syscall.SizeofInotifyEvent * 4096]byte

	for {
		n, err := syscall.Read(w.fd, buf[:])
		// See if there is a message on the "done" channel
		var done bool
		select {
		case done = <-w.done:
		default:
		}

		// If EOF or a "done" message is received
		if n == 0 || done {
			// The syscall.Close can be slow.  Close
			// w.Event first.
			close(w.Event)
			err := syscall.Close(w.fd)
			if err != nil {
				w.Error <- os.NewSyscallError("close", err)
			}
			close(w.Error)
			return
		}
		if n < 0 {
			w.Error <- os.NewSyscallError("read", err)
			continue
		}
		if n < syscall.SizeofInotifyEvent {
			w.Error <- errors.New("inotify: short read in readEvents()")
			continue
		}

		var offset uint32 = 0
		// We don't know how many events we just read into the buffer
		// While the offset points to at least one whole event...
		for offset <= uint32(n-syscall.SizeofInotifyEvent) {
			// Point "raw" to the event in the buffer
			raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset]))
			event := new(Event)
			event.Mask = uint32(raw.Mask)
			event.Cookie = uint32(raw.Cookie)
			nameLen := uint32(raw.Len)
			// If the event happened to the watched directory or the watched file, the kernel
			// doesn't append the filename to the event, but we would like to always fill the
			// the "Name" field with a valid filename. We retrieve the path of the watch from
			// the "paths" map.
			w.mu.Lock()
			event.Name = w.paths[int(raw.Wd)]
			w.mu.Unlock()
			if nameLen > 0 {
				// Point "bytes" at the first byte of the filename
				bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent]))
				// The filename is padded with NUL bytes. TrimRight() gets rid of those.
				event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
			}
			// Send the event on the events channel
			w.Event <- event

			// Move to the next event in the buffer
			offset += syscall.SizeofInotifyEvent + nameLen
		}
	}
}

// String formats the event e in the form
// "filename: 0xEventMask = IN_ACCESS|IN_ATTRIB_|..."
func (e *Event) String() string {
	var events string = ""

	m := e.Mask
	for _, b := range eventBits {
		if m&b.Value == b.Value {
			m &^= b.Value
			events += "|" + b.Name
		}
	}

	if m != 0 {
		events += fmt.Sprintf("|%#x", m)
	}
	if len(events) > 0 {
		events = " == " + events[1:]
	}

	return fmt.Sprintf("%q: %#x%s", e.Name, e.Mask, events)
}

const (
	// Options for inotify_init() are not exported
	// IN_CLOEXEC    uint32 = syscall.IN_CLOEXEC
	// IN_NONBLOCK   uint32 = syscall.IN_NONBLOCK

	// Options for AddWatch
	IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW
	IN_ONESHOT     uint32 = syscall.IN_ONESHOT
	IN_ONLYDIR     uint32 = syscall.IN_ONLYDIR

	// The "IN_MASK_ADD" option is not exported, as AddWatch
	// adds it automatically, if there is already a watch for the given path
	// IN_MASK_ADD      uint32 = syscall.IN_MASK_ADD

	// Events
	IN_ACCESS        uint32 = syscall.IN_ACCESS
	IN_ALL_EVENTS    uint32 = syscall.IN_ALL_EVENTS
	IN_ATTRIB        uint32 = syscall.IN_ATTRIB
	IN_CLOSE         uint32 = syscall.IN_CLOSE
	IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE
	IN_CLOSE_WRITE   uint32 = syscall.IN_CLOSE_WRITE
	IN_CREATE        uint32 = syscall.IN_CREATE
	IN_DELETE        uint32 = syscall.IN_DELETE
	IN_DELETE_SELF   uint32 = syscall.IN_DELETE_SELF
	IN_MODIFY        uint32 = syscall.IN_MODIFY
	IN_MOVE          uint32 = syscall.IN_MOVE
	IN_MOVED_FROM    uint32 = syscall.IN_MOVED_FROM
	IN_MOVED_TO      uint32 = syscall.IN_MOVED_TO
	IN_MOVE_SELF     uint32 = syscall.IN_MOVE_SELF
	IN_OPEN          uint32 = syscall.IN_OPEN

	// Special events
	IN_ISDIR      uint32 = syscall.IN_ISDIR
	IN_IGNORED    uint32 = syscall.IN_IGNORED
	IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW
	IN_UNMOUNT    uint32 = syscall.IN_UNMOUNT
)

var eventBits = []struct {
	Value uint32
	Name  string
}{
	{IN_ACCESS, "IN_ACCESS"},
	{IN_ATTRIB, "IN_ATTRIB"},
	{IN_CLOSE, "IN_CLOSE"},
	{IN_CLOSE_NOWRITE, "IN_CLOSE_NOWRITE"},
	{IN_CLOSE_WRITE, "IN_CLOSE_WRITE"},
	{IN_CREATE, "IN_CREATE"},
	{IN_DELETE, "IN_DELETE"},
	{IN_DELETE_SELF, "IN_DELETE_SELF"},
	{IN_MODIFY, "IN_MODIFY"},
	{IN_MOVE, "IN_MOVE"},
	{IN_MOVED_FROM, "IN_MOVED_FROM"},
	{IN_MOVED_TO, "IN_MOVED_TO"},
	{IN_MOVE_SELF, "IN_MOVE_SELF"},
	{IN_OPEN, "IN_OPEN"},
	{IN_ISDIR, "IN_ISDIR"},
	{IN_IGNORED, "IN_IGNORED"},
	{IN_Q_OVERFLOW, "IN_Q_OVERFLOW"},
	{IN_UNMOUNT, "IN_UNMOUNT"},
}
<commit_msg>inotify: fix memory leak in Watcher<commit_after>// Copyright 2010 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 inotify implements a wrapper for the Linux inotify system.

Example:
    watcher, err := inotify.NewWatcher()
    if err != nil {
        log.Fatal(err)
    }
    err = watcher.Watch("/tmp")
    if err != nil {
        log.Fatal(err)
    }
    for {
        select {
        case ev := <-watcher.Event:
            log.Println("event:", ev)
        case err := <-watcher.Error:
            log.Println("error:", err)
        }
    }

*/
package inotify // import "golang.org/x/exp/inotify"

import (
	"errors"
	"fmt"
	"os"
	"strings"
	"sync"
	"syscall"
	"unsafe"
)

type Event struct {
	Mask   uint32 // Mask of events
	Cookie uint32 // Unique cookie associating related events (for rename(2))
	Name   string // File name (optional)
}

type watch struct {
	wd    uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
	flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
}

type Watcher struct {
	mu       sync.Mutex
	fd       int               // File descriptor (as returned by the inotify_init() syscall)
	watches  map[string]*watch // Map of inotify watches (key: path)
	paths    map[int]string    // Map of watched paths (key: watch descriptor)
	Error    chan error        // Errors are sent on this channel
	Event    chan *Event       // Events are returned on this channel
	done     chan bool         // Channel for sending a "quit message" to the reader goroutine
	isClosed bool              // Set to true when Close() is first called
}

// NewWatcher creates and returns a new inotify instance using inotify_init(2)
func NewWatcher() (*Watcher, error) {
	fd, errno := syscall.InotifyInit()
	if fd == -1 {
		return nil, os.NewSyscallError("inotify_init", errno)
	}
	w := &Watcher{
		fd:      fd,
		watches: make(map[string]*watch),
		paths:   make(map[int]string),
		Event:   make(chan *Event),
		Error:   make(chan error),
		done:    make(chan bool, 1),
	}

	go w.readEvents()
	return w, nil
}

// Close closes an inotify watcher instance
// It sends a message to the reader goroutine to quit and removes all watches
// associated with the inotify instance
func (w *Watcher) Close() error {
	if w.isClosed {
		return nil
	}
	w.isClosed = true

	// Send "quit" message to the reader goroutine
	w.done <- true
	for path := range w.watches {
		w.RemoveWatch(path)
	}

	return nil
}

// AddWatch adds path to the watched file set.
// The flags are interpreted as described in inotify_add_watch(2).
func (w *Watcher) AddWatch(path string, flags uint32) error {
	if w.isClosed {
		return errors.New("inotify instance already closed")
	}

	watchEntry, found := w.watches[path]
	if found {
		watchEntry.flags |= flags
		flags |= syscall.IN_MASK_ADD
	}

	w.mu.Lock() // synchronize with readEvents goroutine

	wd, err := syscall.InotifyAddWatch(w.fd, path, flags)
	if err != nil {
		w.mu.Unlock()
		return &os.PathError{
			Op:   "inotify_add_watch",
			Path: path,
			Err:  err,
		}
	}

	if !found {
		w.watches[path] = &watch{wd: uint32(wd), flags: flags}
		w.paths[wd] = path
	}
	w.mu.Unlock()
	return nil
}

// Watch adds path to the watched file set, watching all events.
func (w *Watcher) Watch(path string) error {
	return w.AddWatch(path, IN_ALL_EVENTS)
}

// RemoveWatch removes path from the watched file set.
func (w *Watcher) RemoveWatch(path string) error {
	watch, ok := w.watches[path]
	if !ok {
		return errors.New(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path))
	}
	success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
	if success == -1 {
		return os.NewSyscallError("inotify_rm_watch", errno)
	}
	delete(w.watches, path)
	// Locking here to protect the read from paths in readEvents.
	w.mu.Lock()
	delete(w.paths, int(watch.wd))
	w.mu.Unlock()
	return nil
}

// readEvents reads from the inotify file descriptor, converts the
// received events into Event objects and sends them via the Event channel
func (w *Watcher) readEvents() {
	var buf [syscall.SizeofInotifyEvent * 4096]byte

	for {
		n, err := syscall.Read(w.fd, buf[:])
		// See if there is a message on the "done" channel
		var done bool
		select {
		case done = <-w.done:
		default:
		}

		// If EOF or a "done" message is received
		if n == 0 || done {
			// The syscall.Close can be slow.  Close
			// w.Event first.
			close(w.Event)
			err := syscall.Close(w.fd)
			if err != nil {
				w.Error <- os.NewSyscallError("close", err)
			}
			close(w.Error)
			return
		}
		if n < 0 {
			w.Error <- os.NewSyscallError("read", err)
			continue
		}
		if n < syscall.SizeofInotifyEvent {
			w.Error <- errors.New("inotify: short read in readEvents()")
			continue
		}

		var offset uint32 = 0
		// We don't know how many events we just read into the buffer
		// While the offset points to at least one whole event...
		for offset <= uint32(n-syscall.SizeofInotifyEvent) {
			// Point "raw" to the event in the buffer
			raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset]))
			event := new(Event)
			event.Mask = uint32(raw.Mask)
			event.Cookie = uint32(raw.Cookie)
			nameLen := uint32(raw.Len)
			// If the event happened to the watched directory or the watched file, the kernel
			// doesn't append the filename to the event, but we would like to always fill the
			// the "Name" field with a valid filename. We retrieve the path of the watch from
			// the "paths" map.
			w.mu.Lock()
			name, ok := w.paths[int(raw.Wd)]
			w.mu.Unlock()
			if ok {
				event.Name = name
				if nameLen > 0 {
					// Point "bytes" at the first byte of the filename
					bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent]))
					// The filename is padded with NUL bytes. TrimRight() gets rid of those.
					event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
				}
				// Send the event on the events channel
				w.Event <- event
			}
			// Move to the next event in the buffer
			offset += syscall.SizeofInotifyEvent + nameLen
		}
	}
}

// String formats the event e in the form
// "filename: 0xEventMask = IN_ACCESS|IN_ATTRIB_|..."
func (e *Event) String() string {
	var events string = ""

	m := e.Mask
	for _, b := range eventBits {
		if m&b.Value == b.Value {
			m &^= b.Value
			events += "|" + b.Name
		}
	}

	if m != 0 {
		events += fmt.Sprintf("|%#x", m)
	}
	if len(events) > 0 {
		events = " == " + events[1:]
	}

	return fmt.Sprintf("%q: %#x%s", e.Name, e.Mask, events)
}

const (
	// Options for inotify_init() are not exported
	// IN_CLOEXEC    uint32 = syscall.IN_CLOEXEC
	// IN_NONBLOCK   uint32 = syscall.IN_NONBLOCK

	// Options for AddWatch
	IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW
	IN_ONESHOT     uint32 = syscall.IN_ONESHOT
	IN_ONLYDIR     uint32 = syscall.IN_ONLYDIR

	// The "IN_MASK_ADD" option is not exported, as AddWatch
	// adds it automatically, if there is already a watch for the given path
	// IN_MASK_ADD      uint32 = syscall.IN_MASK_ADD

	// Events
	IN_ACCESS        uint32 = syscall.IN_ACCESS
	IN_ALL_EVENTS    uint32 = syscall.IN_ALL_EVENTS
	IN_ATTRIB        uint32 = syscall.IN_ATTRIB
	IN_CLOSE         uint32 = syscall.IN_CLOSE
	IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE
	IN_CLOSE_WRITE   uint32 = syscall.IN_CLOSE_WRITE
	IN_CREATE        uint32 = syscall.IN_CREATE
	IN_DELETE        uint32 = syscall.IN_DELETE
	IN_DELETE_SELF   uint32 = syscall.IN_DELETE_SELF
	IN_MODIFY        uint32 = syscall.IN_MODIFY
	IN_MOVE          uint32 = syscall.IN_MOVE
	IN_MOVED_FROM    uint32 = syscall.IN_MOVED_FROM
	IN_MOVED_TO      uint32 = syscall.IN_MOVED_TO
	IN_MOVE_SELF     uint32 = syscall.IN_MOVE_SELF
	IN_OPEN          uint32 = syscall.IN_OPEN

	// Special events
	IN_ISDIR      uint32 = syscall.IN_ISDIR
	IN_IGNORED    uint32 = syscall.IN_IGNORED
	IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW
	IN_UNMOUNT    uint32 = syscall.IN_UNMOUNT
)

var eventBits = []struct {
	Value uint32
	Name  string
}{
	{IN_ACCESS, "IN_ACCESS"},
	{IN_ATTRIB, "IN_ATTRIB"},
	{IN_CLOSE, "IN_CLOSE"},
	{IN_CLOSE_NOWRITE, "IN_CLOSE_NOWRITE"},
	{IN_CLOSE_WRITE, "IN_CLOSE_WRITE"},
	{IN_CREATE, "IN_CREATE"},
	{IN_DELETE, "IN_DELETE"},
	{IN_DELETE_SELF, "IN_DELETE_SELF"},
	{IN_MODIFY, "IN_MODIFY"},
	{IN_MOVE, "IN_MOVE"},
	{IN_MOVED_FROM, "IN_MOVED_FROM"},
	{IN_MOVED_TO, "IN_MOVED_TO"},
	{IN_MOVE_SELF, "IN_MOVE_SELF"},
	{IN_OPEN, "IN_OPEN"},
	{IN_ISDIR, "IN_ISDIR"},
	{IN_IGNORED, "IN_IGNORED"},
	{IN_Q_OVERFLOW, "IN_Q_OVERFLOW"},
	{IN_UNMOUNT, "IN_UNMOUNT"},
}
<|endoftext|>


<commit_before>// Copyright 2016 Google Inc. All Rights Reserved.
//
// 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 render

import (
	"testing"

	"github.com/googlecodelabs/tools/claat/nodes"
)

func TestHTMLEnv(t *testing.T) {
	one := nodes.NewTextNode("one ")
	one.MutateEnv([]string{"one"})
	two := nodes.NewTextNode("two ")
	two.MutateEnv([]string{"two"})
	three := nodes.NewTextNode("three ")
	three.MutateEnv([]string{"one", "three"})

	tests := []struct {
		env    string
		output string
	}{
		{"", "one two three "},
		{"one", "one three "},
		{"two", "two "},
		{"three", "three "},
		{"four", ""},
	}
	for i, test := range tests {
		var ctx Context
		ctx.Env = test.env
		h, err := HTML(ctx, one, two, three)
		if err != nil {
			t.Errorf("%d: %v", i, err)
			continue
		}
		if v := string(h); v != test.output {
			t.Errorf("%d: v = %q; want %q", i, v, test.output)
		}
	}
}

// TODO: test HTML
// TODO: test writeHTML
// TODO: test ReplaceDoubleCurlyBracketsWithEntity
// TODO: test matchEnv
// TODO: test write
// TODO: test writeString
// TODO: test writeFmt
// TODO: test escape
// TODO: test writeEscape
// TODO: test text
// TODO: test image
// TODO: test url
// TODO: test button
// TODO: test code
// TODO: test list
// TODO: test onlyImages
// TODO: test itemsList
// TODO: test grid
// TODO: test infobox
// TODO: test survey
// TODO: test header
// TODO: test youtube
// TODO: test iframe
<commit_msg>Add tests for HTML iframe rendering.<commit_after>// Copyright 2016 Google Inc. All Rights Reserved.
//
// 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 render

import (
	"bytes"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/googlecodelabs/tools/claat/nodes"
)

func TestHTMLEnv(t *testing.T) {
	one := nodes.NewTextNode("one ")
	one.MutateEnv([]string{"one"})
	two := nodes.NewTextNode("two ")
	two.MutateEnv([]string{"two"})
	three := nodes.NewTextNode("three ")
	three.MutateEnv([]string{"one", "three"})

	tests := []struct {
		env    string
		output string
	}{
		{"", "one two three "},
		{"one", "one three "},
		{"two", "two "},
		{"three", "three "},
		{"four", ""},
	}
	for i, test := range tests {
		var ctx Context
		ctx.Env = test.env
		h, err := HTML(ctx, one, two, three)
		if err != nil {
			t.Errorf("%d: %v", i, err)
			continue
		}
		if v := string(h); v != test.output {
			t.Errorf("%d: v = %q; want %q", i, v, test.output)
		}
	}
}

// TODO: test HTML
// TODO: test writeHTML
// TODO: test ReplaceDoubleCurlyBracketsWithEntity
// TODO: test matchEnv
// TODO: test write
// TODO: test writeString
// TODO: test writeFmt
// TODO: test escape
// TODO: test writeEscape
// TODO: test text
// TODO: test image
// TODO: test url
// TODO: test button
// TODO: test code
// TODO: test list
// TODO: test onlyImages
// TODO: test itemsList
// TODO: test grid
// TODO: test infobox
// TODO: test survey
// TODO: test header
// TODO: test youtube

func TestIFrame(t *testing.T) {
	tests := []struct {
		name   string
		inNode *nodes.IframeNode
		out    string
	}{
		{
			name:   "SomeText",
			inNode: nodes.NewIframeNode("maps.google.com"),
			out:    `<iframe class="embedded-iframe" src="maps.google.com"></iframe>`,
		},
		{
			name:   "Escape",
			inNode: nodes.NewIframeNode("ma ps.google.com"),
			out:    `<iframe class="embedded-iframe" src="ma ps.google.com"></iframe>`,
		},
		{
			name:   "Empty",
			inNode: nodes.NewIframeNode(""),
			out:    `<iframe class="embedded-iframe" src=""></iframe>`,
		},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			outBuffer := &bytes.Buffer{}
			hw := &htmlWriter{w: outBuffer}
			hw.iframe(tc.inNode)
			out := outBuffer.String()
			if diff := cmp.Diff(tc.out, out); diff != "" {
				t.Errorf("hw.iframe(%+v) got diff (-want +got):\n%s", tc.inNode, diff)
			}
		})
	}
}
<|endoftext|>


<commit_before>package cli

import (
	"encoding/json"
	"fmt"
	"github.com/gruntwork-io/terragrunt/errors"
	"github.com/gruntwork-io/terragrunt/options"
	"github.com/gruntwork-io/terragrunt/util"
	"github.com/hashicorp/hcl/v2"
	"github.com/hashicorp/hcl/v2/hclwrite"
	"github.com/mattn/go-zglob"
	"github.com/zclconf/go-cty/cty"
	"io/ioutil"
	"path/filepath"
)

func applyAwsProviderPatch(terragruntOptions *options.TerragruntOptions) error {
	if len(terragruntOptions.AwsProviderPatchOverrides) == 0 {
		return errors.WithStackTrace(MissingOverrides(OPT_TERRAGRUNT_OVERRIDE_ATTR))
	}

	terraformFilesInModules, err := findAllTerraformFilesInModules(terragruntOptions)
	if err != nil {
		return err
	}

	for _, terraformFile := range terraformFilesInModules {
		util.Debugf(terragruntOptions.Logger, "Looking at file %s", terraformFile)
		originalTerraformFileContents, err := util.ReadFileAsString(terraformFile)
		if err != nil {
			return err
		}

		updatedTerraformFileContents, codeWasUpdated, err := patchAwsProviderInTerraformCode(originalTerraformFileContents, terraformFile, terragruntOptions.AwsProviderPatchOverrides)
		if err != nil {
			return err
		}

		if codeWasUpdated {
			terragruntOptions.Logger.Printf("Patching AWS provider in %s", terraformFile)
			if err := util.WriteFileWithSamePermissions(terraformFile, terraformFile, []byte(updatedTerraformFileContents)); err != nil {
				return err
			}
		}
	}

	return nil
}

// The format we expect in the .terraform/modules/modules.json file
type TerraformModulesJson struct {
	Modules []TerraformModule
}

type TerraformModule struct {
	Key    string
	Source string
	Dir    string
}

// findAllTerraformFiles returns all Terraform source files within the modules being used by this Terragrunt
// configuration. To be more specific, it only returns the source files downloaded for module "xxx" { ... } blocks into
// the .terraform/modules folder; it does NOT return Terraform files for the top-level (AKA "root") module.
//
// NOTE: this method only supports *.tf files right now. Terraform code defined in *.json files is not currently
// supported.
func findAllTerraformFilesInModules(terragruntOptions *options.TerragruntOptions) ([]string, error) {
	// Terraform downloads modules into the .terraform/modules folder. Unfortunately, it downloads not only the module
	// into that folder, but the entire repo it's in, which can contain lots of other unrelated code we probably don't
	// want to touch. To find the paths to the actual modules, we read the modules.json file in that folder, which is
	// a manifest file Terraform uses to track where the modules are within each repo. Note that this is an internal
	// API, so the way we parse/read this modules.json file may break in future Terraform versions. Note that we
	// can't use the official HashiCorp code to parse this file, as it's marked internal:
	// https://github.com/hashicorp/terraform/blob/master/internal/modsdir/manifest.go
	modulesJsonPath := util.JoinPath(terragruntOptions.DataDir(), "modules", "modules.json")

	if !util.FileExists(modulesJsonPath) {
		return nil, nil
	}

	modulesJsonContents, err := ioutil.ReadFile(modulesJsonPath)
	if err != nil {
		return nil, errors.WithStackTrace(err)
	}

	var terraformModulesJson TerraformModulesJson
	if err := json.Unmarshal(modulesJsonContents, &terraformModulesJson); err != nil {
		return nil, errors.WithStackTrace(err)
	}

	var terraformFiles []string

	for _, module := range terraformModulesJson.Modules {
		if module.Key != "" && module.Dir != "" {
			moduleAbsPath := module.Dir
			if !filepath.IsAbs(moduleAbsPath) {
				moduleAbsPath = util.JoinPath(terragruntOptions.WorkingDir, moduleAbsPath)
			}

			// Ideally, we'd use a builtin Go library like filepath.Glob here, but per https://github.com/golang/go/issues/11862,
			// the current go implementation doesn't support treating ** as zero or more directories, just zero or one.
			// So we use a third-party library.
			matches, err := zglob.Glob(fmt.Sprintf("%s/**/*.tf", moduleAbsPath))
			if err != nil {
				return nil, errors.WithStackTrace(err)
			}

			terraformFiles = append(terraformFiles, matches...)
		}
	}

	return terraformFiles, nil
}

// patchAwsProviderInTerraformCode looks for provider "aws" { ... } blocks in the given Terraform code and overwrites
// the attributes in those provider blocks with the given attributes. It returns the new Terraform code and a boolean
// true if that code was updated.
//
// For example, if you passed in the following Terraform code:
//
// provider "aws" {
//    region = var.aws_region
// }
//
// And you set attributesToOverride to map[string]string{"region": "us-east-1"}, then this method will return:
//
// provider "aws" {
//    region = "us-east-1"
// }
//
// This is a temporary workaround for a Terraform bug (https://github.com/hashicorp/terraform/issues/13018) where
// any dynamic values in nested provider blocks are not handled correctly when you call 'terraform import', so by
// temporarily hard-coding them, we can allow 'import' to work.
func patchAwsProviderInTerraformCode(terraformCode string, terraformFilePath string, attributesToOverride map[string]string) (string, bool, error) {
	if len(attributesToOverride) == 0 {
		return terraformCode, false, nil
	}

	hclFile, err := hclwrite.ParseConfig([]byte(terraformCode), terraformFilePath, hcl.InitialPos)
	if err != nil {
		return "", false, errors.WithStackTrace(err)
	}

	codeWasUpdated := false

	for _, block := range hclFile.Body().Blocks() {
		if block.Type() == "provider" && len(block.Labels()) == 1 && block.Labels()[0] == "aws" {
			for key, value := range attributesToOverride {
				block.Body().SetAttributeValue(key, cty.StringVal(value))
			}

			codeWasUpdated = true
		}
	}

	return string(hclFile.Bytes()), codeWasUpdated, nil
}

// Custom error types

type MissingOverrides string

func (err MissingOverrides) Error() string {
	return fmt.Sprintf("You must specify at least one provider attribute to override via the --%s option.", string(err))
}
<commit_msg>Add missing comment to code<commit_after>package cli

import (
	"encoding/json"
	"fmt"
	"github.com/gruntwork-io/terragrunt/errors"
	"github.com/gruntwork-io/terragrunt/options"
	"github.com/gruntwork-io/terragrunt/util"
	"github.com/hashicorp/hcl/v2"
	"github.com/hashicorp/hcl/v2/hclwrite"
	"github.com/mattn/go-zglob"
	"github.com/zclconf/go-cty/cty"
	"io/ioutil"
	"path/filepath"
)

// applyAwsProviderPatch finds all Terraform modules nested in the current code (i.e., in the .terraform/modules
// folder), looks for provider "aws" { ... } blocks in those modules, and overwrites the attributes in those provider
// blocks with the attributes specified in terragrntOptions.
//
// For example, if were running Terragrunt against code that contained a module:
//
// module "example" {
//   source = "<URL>"
// }
//
// When you run 'init', Terraform would download the code for that module into .terraform/modules. This function would
// scan that module code for provider blocks:
//
// provider "aws" {
//    region = var.aws_region
// }
//
// And if AwsProviderPatchOverrides in terragruntOptions was set to map[string]string{"region": "us-east-1"}, then this
// method would update the module code to:
//
// provider "aws" {
//    region = "us-east-1"
// }
//
// This is a temporary workaround for a Terraform bug (https://github.com/hashicorp/terraform/issues/13018) where
// any dynamic values in nested provider blocks are not handled correctly when you call 'terraform import', so by
// temporarily hard-coding them, we can allow 'import' to work.
func applyAwsProviderPatch(terragruntOptions *options.TerragruntOptions) error {
	if len(terragruntOptions.AwsProviderPatchOverrides) == 0 {
		return errors.WithStackTrace(MissingOverrides(OPT_TERRAGRUNT_OVERRIDE_ATTR))
	}

	terraformFilesInModules, err := findAllTerraformFilesInModules(terragruntOptions)
	if err != nil {
		return err
	}

	for _, terraformFile := range terraformFilesInModules {
		util.Debugf(terragruntOptions.Logger, "Looking at file %s", terraformFile)
		originalTerraformFileContents, err := util.ReadFileAsString(terraformFile)
		if err != nil {
			return err
		}

		updatedTerraformFileContents, codeWasUpdated, err := patchAwsProviderInTerraformCode(originalTerraformFileContents, terraformFile, terragruntOptions.AwsProviderPatchOverrides)
		if err != nil {
			return err
		}

		if codeWasUpdated {
			terragruntOptions.Logger.Printf("Patching AWS provider in %s", terraformFile)
			if err := util.WriteFileWithSamePermissions(terraformFile, terraformFile, []byte(updatedTerraformFileContents)); err != nil {
				return err
			}
		}
	}

	return nil
}

// The format we expect in the .terraform/modules/modules.json file
type TerraformModulesJson struct {
	Modules []TerraformModule
}

type TerraformModule struct {
	Key    string
	Source string
	Dir    string
}

// findAllTerraformFiles returns all Terraform source files within the modules being used by this Terragrunt
// configuration. To be more specific, it only returns the source files downloaded for module "xxx" { ... } blocks into
// the .terraform/modules folder; it does NOT return Terraform files for the top-level (AKA "root") module.
//
// NOTE: this method only supports *.tf files right now. Terraform code defined in *.json files is not currently
// supported.
func findAllTerraformFilesInModules(terragruntOptions *options.TerragruntOptions) ([]string, error) {
	// Terraform downloads modules into the .terraform/modules folder. Unfortunately, it downloads not only the module
	// into that folder, but the entire repo it's in, which can contain lots of other unrelated code we probably don't
	// want to touch. To find the paths to the actual modules, we read the modules.json file in that folder, which is
	// a manifest file Terraform uses to track where the modules are within each repo. Note that this is an internal
	// API, so the way we parse/read this modules.json file may break in future Terraform versions. Note that we
	// can't use the official HashiCorp code to parse this file, as it's marked internal:
	// https://github.com/hashicorp/terraform/blob/master/internal/modsdir/manifest.go
	modulesJsonPath := util.JoinPath(terragruntOptions.DataDir(), "modules", "modules.json")

	if !util.FileExists(modulesJsonPath) {
		return nil, nil
	}

	modulesJsonContents, err := ioutil.ReadFile(modulesJsonPath)
	if err != nil {
		return nil, errors.WithStackTrace(err)
	}

	var terraformModulesJson TerraformModulesJson
	if err := json.Unmarshal(modulesJsonContents, &terraformModulesJson); err != nil {
		return nil, errors.WithStackTrace(err)
	}

	var terraformFiles []string

	for _, module := range terraformModulesJson.Modules {
		if module.Key != "" && module.Dir != "" {
			moduleAbsPath := module.Dir
			if !filepath.IsAbs(moduleAbsPath) {
				moduleAbsPath = util.JoinPath(terragruntOptions.WorkingDir, moduleAbsPath)
			}

			// Ideally, we'd use a builtin Go library like filepath.Glob here, but per https://github.com/golang/go/issues/11862,
			// the current go implementation doesn't support treating ** as zero or more directories, just zero or one.
			// So we use a third-party library.
			matches, err := zglob.Glob(fmt.Sprintf("%s/**/*.tf", moduleAbsPath))
			if err != nil {
				return nil, errors.WithStackTrace(err)
			}

			terraformFiles = append(terraformFiles, matches...)
		}
	}

	return terraformFiles, nil
}

// patchAwsProviderInTerraformCode looks for provider "aws" { ... } blocks in the given Terraform code and overwrites
// the attributes in those provider blocks with the given attributes. It returns the new Terraform code and a boolean
// true if that code was updated.
//
// For example, if you passed in the following Terraform code:
//
// provider "aws" {
//    region = var.aws_region
// }
//
// And you set attributesToOverride to map[string]string{"region": "us-east-1"}, then this method will return:
//
// provider "aws" {
//    region = "us-east-1"
// }
//
// This is a temporary workaround for a Terraform bug (https://github.com/hashicorp/terraform/issues/13018) where
// any dynamic values in nested provider blocks are not handled correctly when you call 'terraform import', so by
// temporarily hard-coding them, we can allow 'import' to work.
func patchAwsProviderInTerraformCode(terraformCode string, terraformFilePath string, attributesToOverride map[string]string) (string, bool, error) {
	if len(attributesToOverride) == 0 {
		return terraformCode, false, nil
	}

	hclFile, err := hclwrite.ParseConfig([]byte(terraformCode), terraformFilePath, hcl.InitialPos)
	if err != nil {
		return "", false, errors.WithStackTrace(err)
	}

	codeWasUpdated := false

	for _, block := range hclFile.Body().Blocks() {
		if block.Type() == "provider" && len(block.Labels()) == 1 && block.Labels()[0] == "aws" {
			for key, value := range attributesToOverride {
				block.Body().SetAttributeValue(key, cty.StringVal(value))
			}

			codeWasUpdated = true
		}
	}

	return string(hclFile.Bytes()), codeWasUpdated, nil
}

// Custom error types

type MissingOverrides string

func (err MissingOverrides) Error() string {
	return fmt.Sprintf("You must specify at least one provider attribute to override via the --%s option.", string(err))
}
<|endoftext|>