Merge pull request #620 from KeyboardNerd/feature/detector
Internally version all detected content by extensionmaster
commit
3c72fa29a6
@ -0,0 +1,287 @@
|
||||
// Copyright 2018 clair authors
|
||||
//
|
||||
// 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 database
|
||||
|
||||
import (
|
||||
"github.com/deckarep/golang-set"
|
||||
)
|
||||
|
||||
// DeduplicateNamespaces deduplicates a list of namespaces.
|
||||
func DeduplicateNamespaces(namespaces ...Namespace) []Namespace {
|
||||
nsSet := mapset.NewSet()
|
||||
for _, ns := range namespaces {
|
||||
nsSet.Add(ns)
|
||||
}
|
||||
|
||||
uniqueNamespaces := make([]Namespace, 0, nsSet.Cardinality())
|
||||
for ns := range nsSet.Iter() {
|
||||
uniqueNamespaces = append(uniqueNamespaces, ns.(Namespace))
|
||||
}
|
||||
|
||||
return uniqueNamespaces
|
||||
}
|
||||
|
||||
// DeduplicateFeatures deduplicates a list of list of features.
|
||||
func DeduplicateFeatures(features ...Feature) []Feature {
|
||||
fSet := mapset.NewSet()
|
||||
for _, f := range features {
|
||||
fSet.Add(f)
|
||||
}
|
||||
|
||||
uniqueFeatures := make([]Feature, 0, fSet.Cardinality())
|
||||
for f := range fSet.Iter() {
|
||||
uniqueFeatures = append(uniqueFeatures, f.(Feature))
|
||||
}
|
||||
|
||||
return uniqueFeatures
|
||||
}
|
||||
|
||||
// PersistPartialLayerAndCommit wraps session PersistLayer function with begin and
|
||||
// commit.
|
||||
func PersistPartialLayerAndCommit(datastore Datastore, layer *Layer) error {
|
||||
tx, err := datastore.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
if err := tx.PersistLayer(layer.Hash, layer.Features, layer.Namespaces, layer.By); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// PersistFeaturesAndCommit wraps session PersistFeaturesAndCommit function with begin and commit.
|
||||
func PersistFeaturesAndCommit(datastore Datastore, features []Feature) error {
|
||||
tx, err := datastore.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
if err := tx.PersistFeatures(features); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// PersistNamespacesAndCommit wraps session PersistNamespaces function with
|
||||
// begin and commit.
|
||||
func PersistNamespacesAndCommit(datastore Datastore, namespaces []Namespace) error {
|
||||
tx, err := datastore.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
if err := tx.PersistNamespaces(namespaces); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// FindAncestryAndRollback wraps session FindAncestry function with begin and
|
||||
// rollback.
|
||||
func FindAncestryAndRollback(datastore Datastore, name string) (Ancestry, bool, error) {
|
||||
tx, err := datastore.Begin()
|
||||
defer tx.Rollback()
|
||||
|
||||
if err != nil {
|
||||
return Ancestry{}, false, err
|
||||
}
|
||||
|
||||
return tx.FindAncestry(name)
|
||||
}
|
||||
|
||||
// FindLayerAndRollback wraps session FindLayer function with begin and rollback.
|
||||
func FindLayerAndRollback(datastore Datastore, hash string) (layer Layer, ok bool, err error) {
|
||||
var tx Session
|
||||
if tx, err = datastore.Begin(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer tx.Rollback()
|
||||
layer, ok, err = tx.FindLayer(hash)
|
||||
return
|
||||
}
|
||||
|
||||
// DeduplicateNamespacedFeatures returns a copy of all unique features in the
|
||||
// input.
|
||||
func DeduplicateNamespacedFeatures(features []NamespacedFeature) []NamespacedFeature {
|
||||
nsSet := mapset.NewSet()
|
||||
for _, ns := range features {
|
||||
nsSet.Add(ns)
|
||||
}
|
||||
|
||||
uniqueFeatures := make([]NamespacedFeature, 0, nsSet.Cardinality())
|
||||
for ns := range nsSet.Iter() {
|
||||
uniqueFeatures = append(uniqueFeatures, ns.(NamespacedFeature))
|
||||
}
|
||||
|
||||
return uniqueFeatures
|
||||
}
|
||||
|
||||
// GetAncestryFeatures returns a list of unique namespaced features in the
|
||||
// ancestry.
|
||||
func GetAncestryFeatures(ancestry Ancestry) []NamespacedFeature {
|
||||
features := []NamespacedFeature{}
|
||||
for _, layer := range ancestry.Layers {
|
||||
features = append(features, layer.GetFeatures()...)
|
||||
}
|
||||
|
||||
return DeduplicateNamespacedFeatures(features)
|
||||
}
|
||||
|
||||
// UpsertAncestryAndCommit wraps session UpsertAncestry function with begin and commit.
|
||||
func UpsertAncestryAndCommit(datastore Datastore, ancestry Ancestry) error {
|
||||
tx, err := datastore.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = tx.UpsertAncestry(ancestry); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PersistNamespacedFeaturesAndCommit wraps session PersistNamespacedFeatures function
|
||||
// with begin and commit.
|
||||
func PersistNamespacedFeaturesAndCommit(datastore Datastore, features []NamespacedFeature) error {
|
||||
tx, err := datastore.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.PersistNamespacedFeatures(features); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CacheRelatedVulnerabilityAndCommit wraps session CacheAffectedNamespacedFeatures
|
||||
// function with begin and commit.
|
||||
func CacheRelatedVulnerabilityAndCommit(datastore Datastore, features []NamespacedFeature) error {
|
||||
tx, err := datastore.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.CacheAffectedNamespacedFeatures(features); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// IntersectDetectors returns the detectors in both d1 and d2.
|
||||
func IntersectDetectors(d1 []Detector, d2 []Detector) []Detector {
|
||||
d1Set := mapset.NewSet()
|
||||
for _, d := range d1 {
|
||||
d1Set.Add(d)
|
||||
}
|
||||
|
||||
d2Set := mapset.NewSet()
|
||||
for _, d := range d2 {
|
||||
d2Set.Add(d)
|
||||
}
|
||||
|
||||
inter := d1Set.Intersect(d2Set)
|
||||
detectors := make([]Detector, 0, inter.Cardinality())
|
||||
for d := range inter.Iter() {
|
||||
detectors = append(detectors, d.(Detector))
|
||||
}
|
||||
|
||||
return detectors
|
||||
}
|
||||
|
||||
// DiffDetectors returns the detectors belongs to d1 but not d2
|
||||
func DiffDetectors(d1 []Detector, d2 []Detector) []Detector {
|
||||
d1Set := mapset.NewSet()
|
||||
for _, d := range d1 {
|
||||
d1Set.Add(d)
|
||||
}
|
||||
|
||||
d2Set := mapset.NewSet()
|
||||
for _, d := range d2 {
|
||||
d2Set.Add(d)
|
||||
}
|
||||
|
||||
diff := d1Set.Difference(d2Set)
|
||||
detectors := make([]Detector, 0, diff.Cardinality())
|
||||
for d := range diff.Iter() {
|
||||
detectors = append(detectors, d.(Detector))
|
||||
}
|
||||
|
||||
return detectors
|
||||
}
|
||||
|
||||
// MergeLayers merges all content in new layer to l, where the content is
|
||||
// updated.
|
||||
func MergeLayers(l *Layer, new *Layer) *Layer {
|
||||
featureSet := mapset.NewSet()
|
||||
namespaceSet := mapset.NewSet()
|
||||
bySet := mapset.NewSet()
|
||||
|
||||
for _, f := range l.Features {
|
||||
featureSet.Add(f)
|
||||
}
|
||||
|
||||
for _, ns := range l.Namespaces {
|
||||
namespaceSet.Add(ns)
|
||||
}
|
||||
|
||||
for _, d := range l.By {
|
||||
bySet.Add(d)
|
||||
}
|
||||
|
||||
for _, feature := range new.Features {
|
||||
if !featureSet.Contains(feature) {
|
||||
l.Features = append(l.Features, feature)
|
||||
featureSet.Add(feature)
|
||||
}
|
||||
}
|
||||
|
||||
for _, namespace := range new.Namespaces {
|
||||
if !namespaceSet.Contains(namespace) {
|
||||
l.Namespaces = append(l.Namespaces, namespace)
|
||||
namespaceSet.Add(namespace)
|
||||
}
|
||||
}
|
||||
|
||||
for _, detector := range new.By {
|
||||
if !bySet.Contains(detector) {
|
||||
l.By = append(l.By, detector)
|
||||
bySet.Add(detector)
|
||||
}
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
// Copyright 2018 clair authors
|
||||
//
|
||||
// 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 database
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// NamespaceDetectorType is a type of detector that extracts the namespaces.
|
||||
NamespaceDetectorType DetectorType = "namespace"
|
||||
// FeatureDetectorType is a type of detector that extracts the features.
|
||||
FeatureDetectorType DetectorType = "feature"
|
||||
)
|
||||
|
||||
// DetectorTypes contains all detector types.
|
||||
var (
|
||||
DetectorTypes = []DetectorType{
|
||||
NamespaceDetectorType,
|
||||
FeatureDetectorType,
|
||||
}
|
||||
// ErrFailedToParseDetectorType is the error returned when a detector type could
|
||||
// not be parsed from a string.
|
||||
ErrFailedToParseDetectorType = errors.New("failed to parse DetectorType from input")
|
||||
// ErrInvalidDetector is the error returned when a detector from database has
|
||||
// invalid name or version or type.
|
||||
ErrInvalidDetector = errors.New("the detector has invalid metadata")
|
||||
)
|
||||
|
||||
// DetectorType is the type of a detector.
|
||||
type DetectorType string
|
||||
|
||||
// Value implements the database/sql/driver.Valuer interface.
|
||||
func (s DetectorType) Value() (driver.Value, error) {
|
||||
return string(s), nil
|
||||
}
|
||||
|
||||
// Scan implements the database/sql.Scanner interface.
|
||||
func (s *DetectorType) Scan(value interface{}) error {
|
||||
val, ok := value.([]byte)
|
||||
if !ok {
|
||||
return errors.New("could not scan a Severity from a non-string input")
|
||||
}
|
||||
|
||||
var err error
|
||||
*s, err = NewDetectorType(string(val))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewDetectorType attempts to parse a string into a standard DetectorType
|
||||
// value.
|
||||
func NewDetectorType(s string) (DetectorType, error) {
|
||||
for _, ss := range DetectorTypes {
|
||||
if strings.EqualFold(s, string(ss)) {
|
||||
return ss, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", ErrFailedToParseDetectorType
|
||||
}
|
||||
|
||||
// Valid checks if a detector type is defined.
|
||||
func (s DetectorType) Valid() bool {
|
||||
for _, t := range DetectorTypes {
|
||||
if s == t {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Detector is an versioned Clair extension.
|
||||
type Detector struct {
|
||||
// Name of an extension should be non-empty and uniquely identifies the
|
||||
// extension.
|
||||
Name string
|
||||
// Version of an extension should be non-empty.
|
||||
Version string
|
||||
// DType is the type of the extension and should be one of the types in
|
||||
// DetectorTypes.
|
||||
DType DetectorType
|
||||
}
|
||||
|
||||
// Valid checks if all fields in the detector satisfies the spec.
|
||||
func (d Detector) Valid() bool {
|
||||
if d.Name == "" || d.Version == "" || !d.DType.Valid() {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// String returns a unique string representation of the detector.
|
||||
func (d Detector) String() string {
|
||||
return fmt.Sprintf("%s:%s", d.Name, d.Version)
|
||||
}
|
||||
|
||||
// NewNamespaceDetector returns a new namespace detector.
|
||||
func NewNamespaceDetector(name, version string) Detector {
|
||||
return Detector{
|
||||
Name: name,
|
||||
Version: version,
|
||||
DType: NamespaceDetectorType,
|
||||
}
|
||||
}
|
||||
|
||||
// NewFeatureDetector returns a new feature detector.
|
||||
func NewFeatureDetector(name, version string) Detector {
|
||||
return Detector{
|
||||
Name: name,
|
||||
Version: version,
|
||||
DType: FeatureDetectorType,
|
||||
}
|
||||
}
|
||||
|
||||
// SerializeDetectors returns the string representation of given detectors.
|
||||
func SerializeDetectors(detectors []Detector) []string {
|
||||
strDetectors := []string{}
|
||||
for _, d := range detectors {
|
||||
strDetectors = append(strDetectors, d.String())
|
||||
}
|
||||
|
||||
return strDetectors
|
||||
}
|
@ -0,0 +1,198 @@
|
||||
// Copyright 2018 clair authors
|
||||
//
|
||||
// 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 pgsql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/deckarep/golang-set"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
)
|
||||
|
||||
const (
|
||||
soiDetector = `
|
||||
INSERT INTO detector (name, version, dtype)
|
||||
SELECT CAST ($1 AS TEXT), CAST ($2 AS TEXT), CAST ($3 AS detector_type )
|
||||
WHERE NOT EXISTS (SELECT id FROM detector WHERE name = $1 AND version = $2 AND dtype = $3);`
|
||||
|
||||
selectAncestryDetectors = `
|
||||
SELECT d.name, d.version, d.dtype
|
||||
FROM ancestry_detector, detector AS d
|
||||
WHERE ancestry_detector.detector_id = d.id AND ancestry_detector.ancestry_id = $1;`
|
||||
|
||||
selectLayerDetectors = `
|
||||
SELECT d.name, d.version, d.dtype
|
||||
FROM layer_detector, detector AS d
|
||||
WHERE layer_detector.detector_id = d.id AND layer_detector.layer_id = $1;`
|
||||
|
||||
insertAncestryDetectors = `
|
||||
INSERT INTO ancestry_detector (ancestry_id, detector_id)
|
||||
SELECT $1, $2
|
||||
WHERE NOT EXISTS (SELECT id FROM ancestry_detector WHERE ancestry_id = $1 AND detector_id = $2)`
|
||||
|
||||
persistLayerDetector = `
|
||||
INSERT INTO layer_detector (layer_id, detector_id)
|
||||
SELECT $1, $2
|
||||
WHERE NOT EXISTS (SELECT id FROM layer_detector WHERE layer_id = $1 AND detector_id = $2)`
|
||||
|
||||
findDetectorID = `SELECT id FROM detector WHERE name = $1 AND version = $2 AND dtype = $3`
|
||||
findAllDetectors = `SELECT id, name, version, dtype FROM detector`
|
||||
)
|
||||
|
||||
type detectorMap struct {
|
||||
byID map[int64]database.Detector
|
||||
byValue map[database.Detector]int64
|
||||
}
|
||||
|
||||
func (tx *pgSession) PersistDetectors(detectors []database.Detector) error {
|
||||
for _, d := range detectors {
|
||||
if !d.Valid() {
|
||||
log.WithField("detector", d).Debug("Invalid Detector")
|
||||
return database.ErrInvalidParameters
|
||||
}
|
||||
|
||||
r, err := tx.Exec(soiDetector, d.Name, d.Version, d.DType)
|
||||
if err != nil {
|
||||
return handleError("soiDetector", err)
|
||||
}
|
||||
|
||||
count, err := r.RowsAffected()
|
||||
if err != nil {
|
||||
return handleError("soiDetector", err)
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
log.Debug("detector already exists: ", d)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *pgSession) persistLayerDetector(layerID int64, detectorID int64) error {
|
||||
if _, err := tx.Exec(persistLayerDetector, layerID, detectorID); err != nil {
|
||||
return handleError("persistLayerDetector", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *pgSession) persistLayerDetectors(layerID int64, detectorIDs []int64) error {
|
||||
alreadySaved := mapset.NewSet()
|
||||
for _, id := range detectorIDs {
|
||||
if alreadySaved.Contains(id) {
|
||||
continue
|
||||
}
|
||||
|
||||
alreadySaved.Add(id)
|
||||
if err := tx.persistLayerDetector(layerID, id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *pgSession) insertAncestryDetectors(ancestryID int64, detectorIDs []int64) error {
|
||||
for _, detectorID := range detectorIDs {
|
||||
if _, err := tx.Exec(insertAncestryDetectors, ancestryID, detectorID); err != nil {
|
||||
return handleError("insertAncestryDetectors", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *pgSession) findAncestryDetectors(id int64) ([]database.Detector, error) {
|
||||
detectors, err := tx.getDetectors(selectAncestryDetectors, id)
|
||||
log.WithField("detectors", detectors).Debug("found ancestry detectors")
|
||||
return detectors, err
|
||||
}
|
||||
|
||||
func (tx *pgSession) findLayerDetectors(id int64) ([]database.Detector, error) {
|
||||
detectors, err := tx.getDetectors(selectLayerDetectors, id)
|
||||
log.WithField("detectors", detectors).Debug("found layer detectors")
|
||||
return detectors, err
|
||||
}
|
||||
|
||||
// findDetectorIDs retrieve ids of the detectors from the database, if any is not
|
||||
// found, return the error.
|
||||
func (tx *pgSession) findDetectorIDs(detectors []database.Detector) ([]int64, error) {
|
||||
ids := []int64{}
|
||||
for _, d := range detectors {
|
||||
id := sql.NullInt64{}
|
||||
err := tx.QueryRow(findDetectorID, d.Name, d.Version, d.DType).Scan(&id)
|
||||
if err != nil {
|
||||
return nil, handleError("findDetectorID", err)
|
||||
}
|
||||
|
||||
if !id.Valid {
|
||||
return nil, database.ErrInconsistent
|
||||
}
|
||||
|
||||
ids = append(ids, id.Int64)
|
||||
}
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (tx *pgSession) getDetectors(query string, id int64) ([]database.Detector, error) {
|
||||
rows, err := tx.Query(query, id)
|
||||
if err != nil {
|
||||
return nil, handleError("getDetectors", err)
|
||||
}
|
||||
|
||||
detectors := []database.Detector{}
|
||||
for rows.Next() {
|
||||
d := database.Detector{}
|
||||
err := rows.Scan(&d.Name, &d.Version, &d.DType)
|
||||
if err != nil {
|
||||
return nil, handleError("getDetectors", err)
|
||||
}
|
||||
|
||||
if !d.Valid() {
|
||||
return nil, database.ErrInvalidDetector
|
||||
}
|
||||
|
||||
detectors = append(detectors, d)
|
||||
}
|
||||
|
||||
return detectors, nil
|
||||
}
|
||||
|
||||
func (tx *pgSession) findAllDetectors() (detectorMap, error) {
|
||||
rows, err := tx.Query(findAllDetectors)
|
||||
if err != nil {
|
||||
return detectorMap{}, handleError("searchAllDetectors", err)
|
||||
}
|
||||
|
||||
detectors := detectorMap{byID: make(map[int64]database.Detector), byValue: make(map[database.Detector]int64)}
|
||||
for rows.Next() {
|
||||
var (
|
||||
id int64
|
||||
d database.Detector
|
||||
)
|
||||
if err := rows.Scan(&id, &d.Name, &d.Version, &d.DType); err != nil {
|
||||
return detectorMap{}, handleError("searchAllDetectors", err)
|
||||
}
|
||||
|
||||
detectors.byID[id] = d
|
||||
detectors.byValue[d] = id
|
||||
}
|
||||
|
||||
return detectors, nil
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
// Copyright 2018 clair authors
|
||||
//
|
||||
// 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 pgsql
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/deckarep/golang-set"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
)
|
||||
|
||||
func testGetAllDetectors(tx *pgSession) []database.Detector {
|
||||
query := `SELECT name, version, dtype FROM detector`
|
||||
rows, err := tx.Query(query)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
detectors := []database.Detector{}
|
||||
for rows.Next() {
|
||||
d := database.Detector{}
|
||||
if err := rows.Scan(&d.Name, &d.Version, &d.DType); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
detectors = append(detectors, d)
|
||||
}
|
||||
|
||||
return detectors
|
||||
}
|
||||
|
||||
var persistDetectorTests = []struct {
|
||||
title string
|
||||
in []database.Detector
|
||||
err string
|
||||
}{
|
||||
{
|
||||
title: "invalid detector",
|
||||
in: []database.Detector{
|
||||
{},
|
||||
database.NewFeatureDetector("name", "2.0"),
|
||||
},
|
||||
err: database.ErrInvalidParameters.Error(),
|
||||
},
|
||||
{
|
||||
title: "invalid detector 2",
|
||||
in: []database.Detector{
|
||||
database.NewFeatureDetector("name", "2.0"),
|
||||
{"name", "1.0", "random not valid dtype"},
|
||||
},
|
||||
err: database.ErrInvalidParameters.Error(),
|
||||
},
|
||||
{
|
||||
title: "detectors with some different fields",
|
||||
in: []database.Detector{
|
||||
database.NewFeatureDetector("name", "2.0"),
|
||||
database.NewFeatureDetector("name", "1.0"),
|
||||
database.NewNamespaceDetector("name", "1.0"),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "duplicated detectors (parameter level)",
|
||||
in: []database.Detector{
|
||||
database.NewFeatureDetector("name", "1.0"),
|
||||
database.NewFeatureDetector("name", "1.0"),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "duplicated detectors (db level)",
|
||||
in: []database.Detector{
|
||||
database.NewNamespaceDetector("os-release", "1.0"),
|
||||
database.NewNamespaceDetector("os-release", "1.0"),
|
||||
database.NewFeatureDetector("dpkg", "1.0"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestPersistDetector(t *testing.T) {
|
||||
datastore, tx := openSessionForTest(t, "PersistDetector", true)
|
||||
defer closeTest(t, datastore, tx)
|
||||
|
||||
for _, test := range persistDetectorTests {
|
||||
t.Run(test.title, func(t *testing.T) {
|
||||
err := tx.PersistDetectors(test.in)
|
||||
if test.err != "" {
|
||||
require.EqualError(t, err, test.err)
|
||||
return
|
||||
}
|
||||
|
||||
detectors := testGetAllDetectors(tx)
|
||||
|
||||
// ensure no duplicated detectors
|
||||
detectorSet := mapset.NewSet()
|
||||
for _, d := range detectors {
|
||||
require.False(t, detectorSet.Contains(d), "duplicated: %v", d)
|
||||
detectorSet.Add(d)
|
||||
}
|
||||
|
||||
// ensure all persisted detectors are actually saved
|
||||
for _, d := range test.in {
|
||||
require.True(t, detectorSet.Contains(d), "detector: %v, detectors: %v", d, detectorSet)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package migrations
|
||||
|
||||
import "github.com/remind101/migrate"
|
||||
|
||||
// MigrationQuery contains the Up migration and Down migration in Plain strings.
|
||||
type MigrationQuery struct {
|
||||
Up []string
|
||||
Down []string
|
||||
}
|
||||
|
||||
// ConcatMigrationQueries concats migration queries in the give order.
|
||||
func ConcatMigrationQueries(qs []MigrationQuery) MigrationQuery {
|
||||
r := MigrationQuery{}
|
||||
for _, q := range qs {
|
||||
r.Up = append(r.Up, q.Up...)
|
||||
r.Down = append(r.Down, q.Down...)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// NewSimpleMigration returns a simple migration plan with all provided
|
||||
// migration queries concatted in order.
|
||||
func NewSimpleMigration(id int, qs []MigrationQuery) migrate.Migration {
|
||||
q := ConcatMigrationQueries(qs)
|
||||
return migrate.Migration{
|
||||
ID: id,
|
||||
Up: migrate.Queries(q.Up),
|
||||
Down: migrate.Queries(q.Down),
|
||||
}
|
||||
}
|
@ -0,0 +1,262 @@
|
||||
// Copyright 2018 clair authors
|
||||
//
|
||||
// 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 pgsql
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/coreos/clair/database"
|
||||
"github.com/coreos/clair/pkg/pagination"
|
||||
)
|
||||
|
||||
// int keys must be the consistent with the database ID.
|
||||
var (
|
||||
realFeatures = map[int]database.Feature{
|
||||
1: {"ourchat", "0.5", "dpkg"},
|
||||
2: {"openssl", "1.0", "dpkg"},
|
||||
3: {"openssl", "2.0", "dpkg"},
|
||||
4: {"fake", "2.0", "rpm"},
|
||||
}
|
||||
|
||||
realNamespaces = map[int]database.Namespace{
|
||||
1: {"debian:7", "dpkg"},
|
||||
2: {"debian:8", "dpkg"},
|
||||
3: {"fake:1.0", "rpm"},
|
||||
}
|
||||
|
||||
realNamespacedFeatures = map[int]database.NamespacedFeature{
|
||||
1: {realFeatures[1], realNamespaces[1]},
|
||||
2: {realFeatures[2], realNamespaces[1]},
|
||||
3: {realFeatures[2], realNamespaces[2]},
|
||||
4: {realFeatures[3], realNamespaces[1]},
|
||||
}
|
||||
|
||||
realDetectors = map[int]database.Detector{
|
||||
1: database.NewNamespaceDetector("os-release", "1.0"),
|
||||
2: database.NewFeatureDetector("dpkg", "1.0"),
|
||||
3: database.NewFeatureDetector("rpm", "1.0"),
|
||||
4: database.NewNamespaceDetector("apt-sources", "1.0"),
|
||||
}
|
||||
|
||||
realLayers = map[int]database.Layer{
|
||||
2: {
|
||||
Hash: "layer-1",
|
||||
By: []database.Detector{realDetectors[1], realDetectors[2]},
|
||||
Features: []database.LayerFeature{
|
||||
{realFeatures[1], realDetectors[2]},
|
||||
{realFeatures[2], realDetectors[2]},
|
||||
},
|
||||
Namespaces: []database.LayerNamespace{
|
||||
{realNamespaces[1], realDetectors[1]},
|
||||
},
|
||||
},
|
||||
6: {
|
||||
Hash: "layer-4",
|
||||
By: []database.Detector{realDetectors[1], realDetectors[2], realDetectors[3], realDetectors[4]},
|
||||
Features: []database.LayerFeature{
|
||||
{realFeatures[4], realDetectors[3]},
|
||||
{realFeatures[3], realDetectors[2]},
|
||||
},
|
||||
Namespaces: []database.LayerNamespace{
|
||||
{realNamespaces[1], realDetectors[1]},
|
||||
{realNamespaces[3], realDetectors[4]},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
realAncestries = map[int]database.Ancestry{
|
||||
2: {
|
||||
Name: "ancestry-2",
|
||||
By: []database.Detector{realDetectors[2], realDetectors[1]},
|
||||
Layers: []database.AncestryLayer{
|
||||
{
|
||||
"layer-0",
|
||||
[]database.AncestryFeature{},
|
||||
},
|
||||
{
|
||||
"layer-1",
|
||||
[]database.AncestryFeature{},
|
||||
},
|
||||
{
|
||||
"layer-2",
|
||||
[]database.AncestryFeature{
|
||||
{
|
||||
realNamespacedFeatures[1],
|
||||
realDetectors[2],
|
||||
realDetectors[1],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"layer-3b",
|
||||
[]database.AncestryFeature{
|
||||
{
|
||||
realNamespacedFeatures[3],
|
||||
realDetectors[2],
|
||||
realDetectors[1],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
realVulnerability = map[int]database.Vulnerability{
|
||||
1: {
|
||||
Name: "CVE-OPENSSL-1-DEB7",
|
||||
Namespace: realNamespaces[1],
|
||||
Description: "A vulnerability affecting OpenSSL < 2.0 on Debian 7.0",
|
||||
Link: "http://google.com/#q=CVE-OPENSSL-1-DEB7",
|
||||
Severity: database.HighSeverity,
|
||||
},
|
||||
2: {
|
||||
Name: "CVE-NOPE",
|
||||
Namespace: realNamespaces[1],
|
||||
Description: "A vulnerability affecting nothing",
|
||||
Severity: database.UnknownSeverity,
|
||||
},
|
||||
}
|
||||
|
||||
realNotification = map[int]database.VulnerabilityNotification{
|
||||
1: {
|
||||
NotificationHook: database.NotificationHook{
|
||||
Name: "test",
|
||||
},
|
||||
Old: takeVulnerabilityPointerFromMap(realVulnerability, 2),
|
||||
New: takeVulnerabilityPointerFromMap(realVulnerability, 1),
|
||||
},
|
||||
}
|
||||
|
||||
fakeFeatures = map[int]database.Feature{
|
||||
1: {
|
||||
Name: "ourchat",
|
||||
Version: "0.6",
|
||||
VersionFormat: "dpkg",
|
||||
},
|
||||
}
|
||||
|
||||
fakeNamespaces = map[int]database.Namespace{
|
||||
1: {"green hat", "rpm"},
|
||||
}
|
||||
fakeNamespacedFeatures = map[int]database.NamespacedFeature{
|
||||
1: {
|
||||
Feature: fakeFeatures[0],
|
||||
Namespace: realNamespaces[0],
|
||||
},
|
||||
}
|
||||
|
||||
fakeDetector = map[int]database.Detector{
|
||||
1: {
|
||||
Name: "fake",
|
||||
Version: "1.0",
|
||||
DType: database.FeatureDetectorType,
|
||||
},
|
||||
2: {
|
||||
Name: "fake2",
|
||||
Version: "2.0",
|
||||
DType: database.NamespaceDetectorType,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func takeVulnerabilityPointerFromMap(m map[int]database.Vulnerability, id int) *database.Vulnerability {
|
||||
x := m[id]
|
||||
return &x
|
||||
}
|
||||
|
||||
func takeAncestryPointerFromMap(m map[int]database.Ancestry, id int) *database.Ancestry {
|
||||
x := m[id]
|
||||
return &x
|
||||
}
|
||||
|
||||
func takeLayerPointerFromMap(m map[int]database.Layer, id int) *database.Layer {
|
||||
x := m[id]
|
||||
return &x
|
||||
}
|
||||
|
||||
func listNamespaces(t *testing.T, tx *pgSession) []database.Namespace {
|
||||
rows, err := tx.Query("SELECT name, version_format FROM namespace")
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
namespaces := []database.Namespace{}
|
||||
for rows.Next() {
|
||||
var ns database.Namespace
|
||||
err := rows.Scan(&ns.Name, &ns.VersionFormat)
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
namespaces = append(namespaces, ns)
|
||||
}
|
||||
|
||||
return namespaces
|
||||
}
|
||||
|
||||
func assertVulnerabilityNotificationWithVulnerableEqual(t *testing.T, key pagination.Key, expected, actual *database.VulnerabilityNotificationWithVulnerable) bool {
|
||||
if expected == actual {
|
||||
return true
|
||||
}
|
||||
|
||||
if expected == nil || actual == nil {
|
||||
return assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
return assert.Equal(t, expected.NotificationHook, actual.NotificationHook) &&
|
||||
AssertPagedVulnerableAncestriesEqual(t, key, expected.Old, actual.Old) &&
|
||||
AssertPagedVulnerableAncestriesEqual(t, key, expected.New, actual.New)
|
||||
}
|
||||
|
||||
func AssertPagedVulnerableAncestriesEqual(t *testing.T, key pagination.Key, expected, actual *database.PagedVulnerableAncestries) bool {
|
||||
if expected == actual {
|
||||
return true
|
||||
}
|
||||
|
||||
if expected == nil || actual == nil {
|
||||
return assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
return database.AssertVulnerabilityEqual(t, &expected.Vulnerability, &actual.Vulnerability) &&
|
||||
assert.Equal(t, expected.Limit, actual.Limit) &&
|
||||
assert.Equal(t, mustUnmarshalToken(key, expected.Current), mustUnmarshalToken(key, actual.Current)) &&
|
||||
assert.Equal(t, mustUnmarshalToken(key, expected.Next), mustUnmarshalToken(key, actual.Next)) &&
|
||||
assert.Equal(t, expected.End, actual.End) &&
|
||||
database.AssertIntStringMapEqual(t, expected.Affected, actual.Affected)
|
||||
}
|
||||
|
||||
func mustUnmarshalToken(key pagination.Key, token pagination.Token) Page {
|
||||
if token == pagination.FirstPageToken {
|
||||
return Page{}
|
||||
}
|
||||
|
||||
p := Page{}
|
||||
if err := key.UnmarshalToken(token, &p); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func mustMarshalToken(key pagination.Key, v interface{}) pagination.Token {
|
||||
token, err := key.MarshalToken(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
@ -0,0 +1,297 @@
|
||||
// Copyright 2018 clair authors
|
||||
//
|
||||
// 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 database
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/deckarep/golang-set"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// AssertDetectorsEqual asserts actual detectors are content wise equal to
|
||||
// expected detectors regardless of the ordering.
|
||||
func AssertDetectorsEqual(t *testing.T, expected, actual []Detector) bool {
|
||||
if len(expected) != len(actual) {
|
||||
return assert.Fail(t, "detectors are not equal", "expected: '%v', actual: '%v'", expected, actual)
|
||||
}
|
||||
|
||||
sort.Slice(expected, func(i, j int) bool {
|
||||
return expected[i].String() < expected[j].String()
|
||||
})
|
||||
|
||||
sort.Slice(actual, func(i, j int) bool {
|
||||
return actual[i].String() < actual[j].String()
|
||||
})
|
||||
|
||||
for i := range expected {
|
||||
if expected[i] != actual[i] {
|
||||
return assert.Fail(t, "detectors are not equal", "expected: '%v', actual: '%v'", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// AssertAncestryEqual asserts actual ancestry equals to expected ancestry
|
||||
// content wise.
|
||||
func AssertAncestryEqual(t *testing.T, expected, actual *Ancestry) bool {
|
||||
if expected == actual {
|
||||
return true
|
||||
}
|
||||
|
||||
if actual == nil || expected == nil {
|
||||
return assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
if !assert.Equal(t, expected.Name, actual.Name) || !AssertDetectorsEqual(t, expected.By, actual.By) {
|
||||
return false
|
||||
}
|
||||
|
||||
if assert.Equal(t, len(expected.Layers), len(actual.Layers)) {
|
||||
for index := range expected.Layers {
|
||||
if !AssertAncestryLayerEqual(t, &expected.Layers[index], &actual.Layers[index]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AssertAncestryLayerEqual asserts actual ancestry layer equals to expected
|
||||
// ancestry layer content wise.
|
||||
func AssertAncestryLayerEqual(t *testing.T, expected, actual *AncestryLayer) bool {
|
||||
if !assert.Equal(t, expected.Hash, actual.Hash) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !assert.Equal(t, len(expected.Features), len(actual.Features),
|
||||
"layer: %s\nExpected: %v\n Actual: %v",
|
||||
expected.Hash, expected.Features, actual.Features,
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
// feature -> is in actual layer
|
||||
hitCounter := map[AncestryFeature]bool{}
|
||||
for _, f := range expected.Features {
|
||||
hitCounter[f] = false
|
||||
}
|
||||
|
||||
// if there's no extra features and no duplicated features, since expected
|
||||
// and actual have the same length, their result must equal.
|
||||
for _, f := range actual.Features {
|
||||
v, ok := hitCounter[f]
|
||||
assert.True(t, ok, "unexpected feature %s", f)
|
||||
assert.False(t, v, "duplicated feature %s", f)
|
||||
hitCounter[f] = true
|
||||
}
|
||||
|
||||
for f, visited := range hitCounter {
|
||||
assert.True(t, visited, "missing feature %s", f)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// AssertElementsEqual asserts that content in actual equals to content in
|
||||
// expected array regardless of ordering.
|
||||
//
|
||||
// Note: This function uses interface wise comparison.
|
||||
func AssertElementsEqual(t *testing.T, expected, actual []interface{}) bool {
|
||||
counter := map[interface{}]bool{}
|
||||
for _, f := range expected {
|
||||
counter[f] = false
|
||||
}
|
||||
|
||||
for _, f := range actual {
|
||||
v, ok := counter[f]
|
||||
if !assert.True(t, ok, "unexpected element %v\nExpected: %v\n Actual: %v\n", f, expected, actual) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !assert.False(t, v, "duplicated element %v\nExpected: %v\n Actual: %v\n", f, expected, actual) {
|
||||
return false
|
||||
}
|
||||
|
||||
counter[f] = true
|
||||
}
|
||||
|
||||
for f, visited := range counter {
|
||||
if !assert.True(t, visited, "missing feature %v\nExpected: %v\n Actual: %v\n", f, expected, actual) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// AssertFeaturesEqual asserts content in actual equals content in expected
|
||||
// regardless of ordering.
|
||||
func AssertFeaturesEqual(t *testing.T, expected, actual []Feature) bool {
|
||||
if assert.Len(t, actual, len(expected)) {
|
||||
has := map[Feature]bool{}
|
||||
for _, nf := range expected {
|
||||
has[nf] = false
|
||||
}
|
||||
|
||||
for _, nf := range actual {
|
||||
has[nf] = true
|
||||
}
|
||||
|
||||
for nf, visited := range has {
|
||||
if !assert.True(t, visited, nf.Name+" is expected") {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AssertLayerFeaturesEqual asserts content in actual equals to content in
|
||||
// expected regardless of ordering.
|
||||
func AssertLayerFeaturesEqual(t *testing.T, expected, actual []LayerFeature) bool {
|
||||
if !assert.Len(t, actual, len(expected)) {
|
||||
return false
|
||||
}
|
||||
|
||||
expectedInterfaces := []interface{}{}
|
||||
for _, e := range expected {
|
||||
expectedInterfaces = append(expectedInterfaces, e)
|
||||
}
|
||||
|
||||
actualInterfaces := []interface{}{}
|
||||
for _, a := range actual {
|
||||
actualInterfaces = append(actualInterfaces, a)
|
||||
}
|
||||
|
||||
return AssertElementsEqual(t, expectedInterfaces, actualInterfaces)
|
||||
}
|
||||
|
||||
// AssertNamespacesEqual asserts content in actual equals to content in
|
||||
// expected regardless of ordering.
|
||||
func AssertNamespacesEqual(t *testing.T, expected, actual []Namespace) bool {
|
||||
expectedInterfaces := []interface{}{}
|
||||
for _, e := range expected {
|
||||
expectedInterfaces = append(expectedInterfaces, e)
|
||||
}
|
||||
|
||||
actualInterfaces := []interface{}{}
|
||||
for _, a := range actual {
|
||||
actualInterfaces = append(actualInterfaces, a)
|
||||
}
|
||||
|
||||
return AssertElementsEqual(t, expectedInterfaces, actualInterfaces)
|
||||
}
|
||||
|
||||
// AssertLayerNamespacesEqual asserts content in actual equals to content in
|
||||
// expected regardless of ordering.
|
||||
func AssertLayerNamespacesEqual(t *testing.T, expected, actual []LayerNamespace) bool {
|
||||
expectedInterfaces := []interface{}{}
|
||||
for _, e := range expected {
|
||||
expectedInterfaces = append(expectedInterfaces, e)
|
||||
}
|
||||
|
||||
actualInterfaces := []interface{}{}
|
||||
for _, a := range actual {
|
||||
actualInterfaces = append(actualInterfaces, a)
|
||||
}
|
||||
|
||||
return AssertElementsEqual(t, expectedInterfaces, actualInterfaces)
|
||||
}
|
||||
|
||||
// AssertLayerEqual asserts actual layer equals to expected layer content wise.
|
||||
func AssertLayerEqual(t *testing.T, expected, actual *Layer) bool {
|
||||
if expected == actual {
|
||||
return true
|
||||
}
|
||||
|
||||
if expected == nil || actual == nil {
|
||||
return assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
return assert.Equal(t, expected.Hash, actual.Hash) &&
|
||||
AssertDetectorsEqual(t, expected.By, actual.By) &&
|
||||
AssertLayerFeaturesEqual(t, expected.Features, actual.Features) &&
|
||||
AssertLayerNamespacesEqual(t, expected.Namespaces, actual.Namespaces)
|
||||
}
|
||||
|
||||
// AssertIntStringMapEqual asserts two maps with integer as key and string as
|
||||
// value are equal.
|
||||
func AssertIntStringMapEqual(t *testing.T, expected, actual map[int]string) bool {
|
||||
checked := mapset.NewSet()
|
||||
for k, v := range expected {
|
||||
assert.Equal(t, v, actual[k])
|
||||
checked.Add(k)
|
||||
}
|
||||
|
||||
for k := range actual {
|
||||
if !assert.True(t, checked.Contains(k)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// AssertVulnerabilityEqual asserts two vulnerabilities are equal.
|
||||
func AssertVulnerabilityEqual(t *testing.T, expected, actual *Vulnerability) bool {
|
||||
return assert.Equal(t, expected.Name, actual.Name) &&
|
||||
assert.Equal(t, expected.Link, actual.Link) &&
|
||||
assert.Equal(t, expected.Description, actual.Description) &&
|
||||
assert.Equal(t, expected.Namespace, actual.Namespace) &&
|
||||
assert.Equal(t, expected.Severity, actual.Severity) &&
|
||||
AssertMetadataMapEqual(t, expected.Metadata, actual.Metadata)
|
||||
}
|
||||
|
||||
func castMetadataMapToInterface(metadata MetadataMap) map[string]interface{} {
|
||||
content, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
data := make(map[string]interface{})
|
||||
if err := json.Unmarshal(content, &data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// AssertMetadataMapEqual asserts two metadata maps are equal.
|
||||
func AssertMetadataMapEqual(t *testing.T, expected, actual MetadataMap) bool {
|
||||
expectedMap := castMetadataMapToInterface(expected)
|
||||
actualMap := castMetadataMapToInterface(actual)
|
||||
checked := mapset.NewSet()
|
||||
for k, v := range expectedMap {
|
||||
if !assert.Equal(t, v, (actualMap)[k]) {
|
||||
return false
|
||||
}
|
||||
|
||||
checked.Add(k)
|
||||
}
|
||||
|
||||
for k := range actual {
|
||||
if !assert.True(t, checked.Contains(k)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
@ -0,0 +1,11 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.8
|
||||
- 1.9
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test -race ./...
|
||||
- go test -bench=.
|
||||
|
@ -0,0 +1,22 @@
|
||||
Open Source Initiative OSI - The MIT License (MIT):Licensing
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -0,0 +1,95 @@
|
||||
[![Build Status](https://travis-ci.org/deckarep/golang-set.svg?branch=master)](https://travis-ci.org/deckarep/golang-set)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/deckarep/golang-set)](https://goreportcard.com/report/github.com/deckarep/golang-set)
|
||||
[![GoDoc](https://godoc.org/github.com/deckarep/golang-set?status.svg)](http://godoc.org/github.com/deckarep/golang-set)
|
||||
|
||||
## golang-set
|
||||
|
||||
|
||||
The missing set collection for the Go language. Until Go has sets built-in...use this.
|
||||
|
||||
Coming from Python one of the things I miss is the superbly wonderful set collection. This is my attempt to mimic the primary features of the set from Python.
|
||||
You can of course argue that there is no need for a set in Go, otherwise the creators would have added one to the standard library. To those I say simply ignore this repository
|
||||
and carry-on and to the rest that find this useful please contribute in helping me make it better by:
|
||||
|
||||
* Helping to make more idiomatic improvements to the code.
|
||||
* Helping to increase the performance of it. ~~(So far, no attempt has been made, but since it uses a map internally, I expect it to be mostly performant.)~~
|
||||
* Helping to make the unit-tests more robust and kick-ass.
|
||||
* Helping to fill in the [documentation.](http://godoc.org/github.com/deckarep/golang-set)
|
||||
* Simply offering feedback and suggestions. (Positive, constructive feedback is appreciated.)
|
||||
|
||||
I have to give some credit for helping seed the idea with this post on [stackoverflow.](http://programmers.stackexchange.com/questions/177428/sets-data-structure-in-golang)
|
||||
|
||||
*Update* - as of 3/9/2014, you can use a compile-time generic version of this package in the [gen](http://clipperhouse.github.io/gen/) framework. This framework allows you to use the golang-set in a completely generic and type-safe way by allowing you to generate a supporting .go file based on your custom types.
|
||||
|
||||
## Features (as of 9/22/2014)
|
||||
|
||||
* a CartesianProduct() method has been added with unit-tests: [Read more about the cartesian product](http://en.wikipedia.org/wiki/Cartesian_product)
|
||||
|
||||
## Features (as of 9/15/2014)
|
||||
|
||||
* a PowerSet() method has been added with unit-tests: [Read more about the Power set](http://en.wikipedia.org/wiki/Power_set)
|
||||
|
||||
## Features (as of 4/22/2014)
|
||||
|
||||
* One common interface to both implementations
|
||||
* Two set implementations to choose from
|
||||
* a thread-safe implementation designed for concurrent use
|
||||
* a non-thread-safe implementation designed for performance
|
||||
* 75 benchmarks for both implementations
|
||||
* 35 unit tests for both implementations
|
||||
* 14 concurrent tests for the thread-safe implementation
|
||||
|
||||
|
||||
|
||||
Please see the unit test file for additional usage examples. The Python set documentation will also do a better job than I can of explaining how a set typically [works.](http://docs.python.org/2/library/sets.html) Please keep in mind
|
||||
however that the Python set is a built-in type and supports additional features and syntax that make it awesome.
|
||||
|
||||
## Examples but not exhaustive:
|
||||
|
||||
```go
|
||||
requiredClasses := mapset.NewSet()
|
||||
requiredClasses.Add("Cooking")
|
||||
requiredClasses.Add("English")
|
||||
requiredClasses.Add("Math")
|
||||
requiredClasses.Add("Biology")
|
||||
|
||||
scienceSlice := []interface{}{"Biology", "Chemistry"}
|
||||
scienceClasses := mapset.NewSetFromSlice(scienceSlice)
|
||||
|
||||
electiveClasses := mapset.NewSet()
|
||||
electiveClasses.Add("Welding")
|
||||
electiveClasses.Add("Music")
|
||||
electiveClasses.Add("Automotive")
|
||||
|
||||
bonusClasses := mapset.NewSet()
|
||||
bonusClasses.Add("Go Programming")
|
||||
bonusClasses.Add("Python Programming")
|
||||
|
||||
//Show me all the available classes I can take
|
||||
allClasses := requiredClasses.Union(scienceClasses).Union(electiveClasses).Union(bonusClasses)
|
||||
fmt.Println(allClasses) //Set{Cooking, English, Math, Chemistry, Welding, Biology, Music, Automotive, Go Programming, Python Programming}
|
||||
|
||||
|
||||
//Is cooking considered a science class?
|
||||
fmt.Println(scienceClasses.Contains("Cooking")) //false
|
||||
|
||||
//Show me all classes that are not science classes, since I hate science.
|
||||
fmt.Println(allClasses.Difference(scienceClasses)) //Set{Music, Automotive, Go Programming, Python Programming, Cooking, English, Math, Welding}
|
||||
|
||||
//Which science classes are also required classes?
|
||||
fmt.Println(scienceClasses.Intersect(requiredClasses)) //Set{Biology}
|
||||
|
||||
//How many bonus classes do you offer?
|
||||
fmt.Println(bonusClasses.Cardinality()) //2
|
||||
|
||||
//Do you have the following classes? Welding, Automotive and English?
|
||||
fmt.Println(allClasses.IsSuperset(mapset.NewSetFromSlice([]interface{}{"Welding", "Automotive", "English"}))) //true
|
||||
```
|
||||
|
||||
Thanks!
|
||||
|
||||
-Ralph
|
||||
|
||||
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/deckarep/golang-set/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
|
||||
|
||||
[![Analytics](https://ga-beacon.appspot.com/UA-42584447-2/deckarep/golang-set)](https://github.com/igrigorik/ga-beacon)
|
@ -0,0 +1,674 @@
|
||||
package mapset
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func nrand(n int) []int {
|
||||
i := make([]int, n)
|
||||
for ind := range i {
|
||||
i[ind] = rand.Int()
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func toInterfaces(i []int) []interface{} {
|
||||
ifs := make([]interface{}, len(i))
|
||||
for ind, v := range i {
|
||||
ifs[ind] = v
|
||||
}
|
||||
return ifs
|
||||
}
|
||||
|
||||
func benchAdd(b *testing.B, s Set) {
|
||||
nums := nrand(b.N)
|
||||
b.ResetTimer()
|
||||
for _, v := range nums {
|
||||
s.Add(v)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAddSafe(b *testing.B) {
|
||||
benchAdd(b, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkAddUnsafe(b *testing.B) {
|
||||
benchAdd(b, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func benchRemove(b *testing.B, s Set) {
|
||||
nums := nrand(b.N)
|
||||
for _, v := range nums {
|
||||
s.Add(v)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for _, v := range nums {
|
||||
s.Remove(v)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRemoveSafe(b *testing.B) {
|
||||
benchRemove(b, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkRemoveUnsafe(b *testing.B) {
|
||||
benchRemove(b, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func benchCardinality(b *testing.B, s Set) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.Cardinality()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCardinalitySafe(b *testing.B) {
|
||||
benchCardinality(b, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkCardinalityUnsafe(b *testing.B) {
|
||||
benchCardinality(b, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func benchClear(b *testing.B, s Set) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.Clear()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkClearSafe(b *testing.B) {
|
||||
benchClear(b, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkClearUnsafe(b *testing.B) {
|
||||
benchClear(b, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func benchClone(b *testing.B, n int, s Set) {
|
||||
nums := toInterfaces(nrand(n))
|
||||
for _, v := range nums {
|
||||
s.Add(v)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.Clone()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkClone1Safe(b *testing.B) {
|
||||
benchClone(b, 1, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkClone1Unsafe(b *testing.B) {
|
||||
benchClone(b, 1, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkClone10Safe(b *testing.B) {
|
||||
benchClone(b, 10, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkClone10Unsafe(b *testing.B) {
|
||||
benchClone(b, 10, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkClone100Safe(b *testing.B) {
|
||||
benchClone(b, 100, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkClone100Unsafe(b *testing.B) {
|
||||
benchClone(b, 100, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func benchContains(b *testing.B, n int, s Set) {
|
||||
nums := toInterfaces(nrand(n))
|
||||
for _, v := range nums {
|
||||
s.Add(v)
|
||||
}
|
||||
|
||||
nums[n-1] = -1 // Definitely not in s
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.Contains(nums...)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkContains1Safe(b *testing.B) {
|
||||
benchContains(b, 1, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkContains1Unsafe(b *testing.B) {
|
||||
benchContains(b, 1, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkContains10Safe(b *testing.B) {
|
||||
benchContains(b, 10, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkContains10Unsafe(b *testing.B) {
|
||||
benchContains(b, 10, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkContains100Safe(b *testing.B) {
|
||||
benchContains(b, 100, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkContains100Unsafe(b *testing.B) {
|
||||
benchContains(b, 100, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func benchEqual(b *testing.B, n int, s, t Set) {
|
||||
nums := nrand(n)
|
||||
for _, v := range nums {
|
||||
s.Add(v)
|
||||
t.Add(v)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.Equal(t)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEqual1Safe(b *testing.B) {
|
||||
benchEqual(b, 1, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkEqual1Unsafe(b *testing.B) {
|
||||
benchEqual(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkEqual10Safe(b *testing.B) {
|
||||
benchEqual(b, 10, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkEqual10Unsafe(b *testing.B) {
|
||||
benchEqual(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkEqual100Safe(b *testing.B) {
|
||||
benchEqual(b, 100, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkEqual100Unsafe(b *testing.B) {
|
||||
benchEqual(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func benchDifference(b *testing.B, n int, s, t Set) {
|
||||
nums := nrand(n)
|
||||
for _, v := range nums {
|
||||
s.Add(v)
|
||||
}
|
||||
for _, v := range nums[:n/2] {
|
||||
t.Add(v)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.Difference(t)
|
||||
}
|
||||
}
|
||||
|
||||
func benchIsSubset(b *testing.B, n int, s, t Set) {
|
||||
nums := nrand(n)
|
||||
for _, v := range nums {
|
||||
s.Add(v)
|
||||
t.Add(v)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.IsSubset(t)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsSubset1Safe(b *testing.B) {
|
||||
benchIsSubset(b, 1, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsSubset1Unsafe(b *testing.B) {
|
||||
benchIsSubset(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsSubset10Safe(b *testing.B) {
|
||||
benchIsSubset(b, 10, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsSubset10Unsafe(b *testing.B) {
|
||||
benchIsSubset(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsSubset100Safe(b *testing.B) {
|
||||
benchIsSubset(b, 100, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsSubset100Unsafe(b *testing.B) {
|
||||
benchIsSubset(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func benchIsSuperset(b *testing.B, n int, s, t Set) {
|
||||
nums := nrand(n)
|
||||
for _, v := range nums {
|
||||
s.Add(v)
|
||||
t.Add(v)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.IsSuperset(t)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsSuperset1Safe(b *testing.B) {
|
||||
benchIsSuperset(b, 1, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsSuperset1Unsafe(b *testing.B) {
|
||||
benchIsSuperset(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsSuperset10Safe(b *testing.B) {
|
||||
benchIsSuperset(b, 10, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsSuperset10Unsafe(b *testing.B) {
|
||||
benchIsSuperset(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsSuperset100Safe(b *testing.B) {
|
||||
benchIsSuperset(b, 100, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsSuperset100Unsafe(b *testing.B) {
|
||||
benchIsSuperset(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func benchIsProperSubset(b *testing.B, n int, s, t Set) {
|
||||
nums := nrand(n)
|
||||
for _, v := range nums {
|
||||
s.Add(v)
|
||||
t.Add(v)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.IsProperSubset(t)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsProperSubset1Safe(b *testing.B) {
|
||||
benchIsProperSubset(b, 1, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsProperSubset1Unsafe(b *testing.B) {
|
||||
benchIsProperSubset(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsProperSubset10Safe(b *testing.B) {
|
||||
benchIsProperSubset(b, 10, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsProperSubset10Unsafe(b *testing.B) {
|
||||
benchIsProperSubset(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsProperSubset100Safe(b *testing.B) {
|
||||
benchIsProperSubset(b, 100, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsProperSubset100Unsafe(b *testing.B) {
|
||||
benchIsProperSubset(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func benchIsProperSuperset(b *testing.B, n int, s, t Set) {
|
||||
nums := nrand(n)
|
||||
for _, v := range nums {
|
||||
s.Add(v)
|
||||
t.Add(v)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.IsProperSuperset(t)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsProperSuperset1Safe(b *testing.B) {
|
||||
benchIsProperSuperset(b, 1, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsProperSuperset1Unsafe(b *testing.B) {
|
||||
benchIsProperSuperset(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsProperSuperset10Safe(b *testing.B) {
|
||||
benchIsProperSuperset(b, 10, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsProperSuperset10Unsafe(b *testing.B) {
|
||||
benchIsProperSuperset(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsProperSuperset100Safe(b *testing.B) {
|
||||
benchIsProperSuperset(b, 100, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIsProperSuperset100Unsafe(b *testing.B) {
|
||||
benchIsProperSuperset(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkDifference1Safe(b *testing.B) {
|
||||
benchDifference(b, 1, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkDifference1Unsafe(b *testing.B) {
|
||||
benchDifference(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkDifference10Safe(b *testing.B) {
|
||||
benchDifference(b, 10, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkDifference10Unsafe(b *testing.B) {
|
||||
benchDifference(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkDifference100Safe(b *testing.B) {
|
||||
benchDifference(b, 100, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkDifference100Unsafe(b *testing.B) {
|
||||
benchDifference(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func benchIntersect(b *testing.B, n int, s, t Set) {
|
||||
nums := nrand(int(float64(n) * float64(1.5)))
|
||||
for _, v := range nums[:n] {
|
||||
s.Add(v)
|
||||
}
|
||||
for _, v := range nums[n/2:] {
|
||||
t.Add(v)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.Intersect(t)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIntersect1Safe(b *testing.B) {
|
||||
benchIntersect(b, 1, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIntersect1Unsafe(b *testing.B) {
|
||||
benchIntersect(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkIntersect10Safe(b *testing.B) {
|
||||
benchIntersect(b, 10, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIntersect10Unsafe(b *testing.B) {
|
||||
benchIntersect(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkIntersect100Safe(b *testing.B) {
|
||||
benchIntersect(b, 100, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIntersect100Unsafe(b *testing.B) {
|
||||
benchIntersect(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func benchSymmetricDifference(b *testing.B, n int, s, t Set) {
|
||||
nums := nrand(int(float64(n) * float64(1.5)))
|
||||
for _, v := range nums[:n] {
|
||||
s.Add(v)
|
||||
}
|
||||
for _, v := range nums[n/2:] {
|
||||
t.Add(v)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.SymmetricDifference(t)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSymmetricDifference1Safe(b *testing.B) {
|
||||
benchSymmetricDifference(b, 1, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkSymmetricDifference1Unsafe(b *testing.B) {
|
||||
benchSymmetricDifference(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkSymmetricDifference10Safe(b *testing.B) {
|
||||
benchSymmetricDifference(b, 10, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkSymmetricDifference10Unsafe(b *testing.B) {
|
||||
benchSymmetricDifference(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkSymmetricDifference100Safe(b *testing.B) {
|
||||
benchSymmetricDifference(b, 100, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkSymmetricDifference100Unsafe(b *testing.B) {
|
||||
benchSymmetricDifference(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func benchUnion(b *testing.B, n int, s, t Set) {
|
||||
nums := nrand(n)
|
||||
for _, v := range nums[:n/2] {
|
||||
s.Add(v)
|
||||
}
|
||||
for _, v := range nums[n/2:] {
|
||||
t.Add(v)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.Union(t)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnion1Safe(b *testing.B) {
|
||||
benchUnion(b, 1, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkUnion1Unsafe(b *testing.B) {
|
||||
benchUnion(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkUnion10Safe(b *testing.B) {
|
||||
benchUnion(b, 10, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkUnion10Unsafe(b *testing.B) {
|
||||
benchUnion(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkUnion100Safe(b *testing.B) {
|
||||
benchUnion(b, 100, NewSet(), NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkUnion100Unsafe(b *testing.B) {
|
||||
benchUnion(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func benchEach(b *testing.B, n int, s Set) {
|
||||
nums := nrand(n)
|
||||
for _, v := range nums {
|
||||
s.Add(v)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.Each(func(elem interface{}) bool {
|
||||
return false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEach1Safe(b *testing.B) {
|
||||
benchEach(b, 1, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkEach1Unsafe(b *testing.B) {
|
||||
benchEach(b, 1, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkEach10Safe(b *testing.B) {
|
||||
benchEach(b, 10, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkEach10Unsafe(b *testing.B) {
|
||||
benchEach(b, 10, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkEach100Safe(b *testing.B) {
|
||||
benchEach(b, 100, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkEach100Unsafe(b *testing.B) {
|
||||
benchEach(b, 100, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func benchIter(b *testing.B, n int, s Set) {
|
||||
nums := nrand(n)
|
||||
for _, v := range nums {
|
||||
s.Add(v)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c := s.Iter()
|
||||
for range c {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIter1Safe(b *testing.B) {
|
||||
benchIter(b, 1, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIter1Unsafe(b *testing.B) {
|
||||
benchIter(b, 1, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkIter10Safe(b *testing.B) {
|
||||
benchIter(b, 10, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIter10Unsafe(b *testing.B) {
|
||||
benchIter(b, 10, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkIter100Safe(b *testing.B) {
|
||||
benchIter(b, 100, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIter100Unsafe(b *testing.B) {
|
||||
benchIter(b, 100, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func benchIterator(b *testing.B, n int, s Set) {
|
||||
nums := nrand(n)
|
||||
for _, v := range nums {
|
||||
s.Add(v)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c := s.Iterator().C
|
||||
for range c {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIterator1Safe(b *testing.B) {
|
||||
benchIterator(b, 1, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIterator1Unsafe(b *testing.B) {
|
||||
benchIterator(b, 1, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkIterator10Safe(b *testing.B) {
|
||||
benchIterator(b, 10, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIterator10Unsafe(b *testing.B) {
|
||||
benchIterator(b, 10, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkIterator100Safe(b *testing.B) {
|
||||
benchIterator(b, 100, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkIterator100Unsafe(b *testing.B) {
|
||||
benchIterator(b, 100, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func benchString(b *testing.B, n int, s Set) {
|
||||
nums := nrand(n)
|
||||
for _, v := range nums {
|
||||
s.Add(v)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = s.String()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkString1Safe(b *testing.B) {
|
||||
benchString(b, 1, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkString1Unsafe(b *testing.B) {
|
||||
benchString(b, 1, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkString10Safe(b *testing.B) {
|
||||
benchString(b, 10, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkString10Unsafe(b *testing.B) {
|
||||
benchString(b, 10, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func BenchmarkString100Safe(b *testing.B) {
|
||||
benchString(b, 100, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkString100Unsafe(b *testing.B) {
|
||||
benchString(b, 100, NewThreadUnsafeSet())
|
||||
}
|
||||
|
||||
func benchToSlice(b *testing.B, s Set) {
|
||||
nums := nrand(b.N)
|
||||
for _, v := range nums {
|
||||
s.Add(v)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.ToSlice()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkToSliceSafe(b *testing.B) {
|
||||
benchToSlice(b, NewSet())
|
||||
}
|
||||
|
||||
func BenchmarkToSliceUnsafe(b *testing.B) {
|
||||
benchToSlice(b, NewThreadUnsafeSet())
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
Open Source Initiative OSI - The MIT License (MIT):Licensing
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package mapset
|
||||
|
||||
// Iterator defines an iterator over a Set, its C channel can be used to range over the Set's
|
||||
// elements.
|
||||
type Iterator struct {
|
||||
C <-chan interface{}
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
// Stop stops the Iterator, no further elements will be received on C, C will be closed.
|
||||
func (i *Iterator) Stop() {
|
||||
// Allows for Stop() to be called multiple times
|
||||
// (close() panics when called on already closed channel)
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
|
||||
close(i.stop)
|
||||
|
||||
// Exhaust any remaining elements.
|
||||
for range i.C {
|
||||
}
|
||||
}
|
||||
|
||||
// newIterator returns a new Iterator instance together with its item and stop channels.
|
||||
func newIterator() (*Iterator, chan<- interface{}, <-chan struct{}) {
|
||||
itemChan := make(chan interface{})
|
||||
stopChan := make(chan struct{})
|
||||
return &Iterator{
|
||||
C: itemChan,
|
||||
stop: stopChan,
|
||||
}, itemChan, stopChan
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package mapset
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type YourType struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func ExampleIterator() {
|
||||
set := NewSetFromSlice([]interface{}{
|
||||
&YourType{Name: "Alise"},
|
||||
&YourType{Name: "Bob"},
|
||||
&YourType{Name: "John"},
|
||||
&YourType{Name: "Nick"},
|
||||
})
|
||||
|
||||
var found *YourType
|
||||
it := set.Iterator()
|
||||
|
||||
for elem := range it.C {
|
||||
if elem.(*YourType).Name == "John" {
|
||||
found = elem.(*YourType)
|
||||
it.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Found %+v\n", found)
|
||||
|
||||
// Output: Found &{Name:John}
|
||||
}
|
@ -0,0 +1,217 @@
|
||||
/*
|
||||
Open Source Initiative OSI - The MIT License (MIT):Licensing
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
// Package mapset implements a simple and generic set collection.
|
||||
// Items stored within it are unordered and unique. It supports
|
||||
// typical set operations: membership testing, intersection, union,
|
||||
// difference, symmetric difference and cloning.
|
||||
//
|
||||
// Package mapset provides two implementations of the Set
|
||||
// interface. The default implementation is safe for concurrent
|
||||
// access, but a non-thread-safe implementation is also provided for
|
||||
// programs that can benefit from the slight speed improvement and
|
||||
// that can enforce mutual exclusion through other means.
|
||||
package mapset
|
||||
|
||||
// Set is the primary interface provided by the mapset package. It
|
||||
// represents an unordered set of data and a large number of
|
||||
// operations that can be applied to that set.
|
||||
type Set interface {
|
||||
// Adds an element to the set. Returns whether
|
||||
// the item was added.
|
||||
Add(i interface{}) bool
|
||||
|
||||
// Returns the number of elements in the set.
|
||||
Cardinality() int
|
||||
|
||||
// Removes all elements from the set, leaving
|
||||
// the empty set.
|
||||
Clear()
|
||||
|
||||
// Returns a clone of the set using the same
|
||||
// implementation, duplicating all keys.
|
||||
Clone() Set
|
||||
|
||||
// Returns whether the given items
|
||||
// are all in the set.
|
||||
Contains(i ...interface{}) bool
|
||||
|
||||
// Returns the difference between this set
|
||||
// and other. The returned set will contain
|
||||
// all elements of this set that are not also
|
||||
// elements of other.
|
||||
//
|
||||
// Note that the argument to Difference
|
||||
// must be of the same type as the receiver
|
||||
// of the method. Otherwise, Difference will
|
||||
// panic.
|
||||
Difference(other Set) Set
|
||||
|
||||
// Determines if two sets are equal to each
|
||||
// other. If they have the same cardinality
|
||||
// and contain the same elements, they are
|
||||
// considered equal. The order in which
|
||||
// the elements were added is irrelevant.
|
||||
//
|
||||
// Note that the argument to Equal must be
|
||||
// of the same type as the receiver of the
|
||||
// method. Otherwise, Equal will panic.
|
||||
Equal(other Set) bool
|
||||
|
||||
// Returns a new set containing only the elements
|
||||
// that exist only in both sets.
|
||||
//
|
||||
// Note that the argument to Intersect
|
||||
// must be of the same type as the receiver
|
||||
// of the method. Otherwise, Intersect will
|
||||
// panic.
|
||||
Intersect(other Set) Set
|
||||
|
||||
// Determines if every element in this set is in
|
||||
// the other set but the two sets are not equal.
|
||||
//
|
||||
// Note that the argument to IsProperSubset
|
||||
// must be of the same type as the receiver
|
||||
// of the method. Otherwise, IsProperSubset
|
||||
// will panic.
|
||||
IsProperSubset(other Set) bool
|
||||
|
||||
// Determines if every element in the other set
|
||||
// is in this set but the two sets are not
|
||||
// equal.
|
||||
//
|
||||
// Note that the argument to IsSuperset
|
||||
// must be of the same type as the receiver
|
||||
// of the method. Otherwise, IsSuperset will
|
||||
// panic.
|
||||
IsProperSuperset(other Set) bool
|
||||
|
||||
// Determines if every element in this set is in
|
||||
// the other set.
|
||||
//
|
||||
// Note that the argument to IsSubset
|
||||
// must be of the same type as the receiver
|
||||
// of the method. Otherwise, IsSubset will
|
||||
// panic.
|
||||
IsSubset(other Set) bool
|
||||
|
||||
// Determines if every element in the other set
|
||||
// is in this set.
|
||||
//
|
||||
// Note that the argument to IsSuperset
|
||||
// must be of the same type as the receiver
|
||||
// of the method. Otherwise, IsSuperset will
|
||||
// panic.
|
||||
IsSuperset(other Set) bool
|
||||
|
||||
// Iterates over elements and executes the passed func against each element.
|
||||
// If passed func returns true, stop iteration at the time.
|
||||
Each(func(interface{}) bool)
|
||||
|
||||
// Returns a channel of elements that you can
|
||||
// range over.
|
||||
Iter() <-chan interface{}
|
||||
|
||||
// Returns an Iterator object that you can
|
||||
// use to range over the set.
|
||||
Iterator() *Iterator
|
||||
|
||||
// Remove a single element from the set.
|
||||
Remove(i interface{})
|
||||
|
||||
// Provides a convenient string representation
|
||||
// of the current state of the set.
|
||||
String() string
|
||||
|
||||
// Returns a new set with all elements which are
|
||||
// in either this set or the other set but not in both.
|
||||
//
|
||||
// Note that the argument to SymmetricDifference
|
||||
// must be of the same type as the receiver
|
||||
// of the method. Otherwise, SymmetricDifference
|
||||
// will panic.
|
||||
SymmetricDifference(other Set) Set
|
||||
|
||||
// Returns a new set with all elements in both sets.
|
||||
//
|
||||
// Note that the argument to Union must be of the
|
||||
|
||||
// same type as the receiver of the method.
|
||||
// Otherwise, IsSuperset will panic.
|
||||
Union(other Set) Set
|
||||
|
||||
// Pop removes and returns an arbitrary item from the set.
|
||||
Pop() interface{}
|
||||
|
||||
// Returns all subsets of a given set (Power Set).
|
||||
PowerSet() Set
|
||||
|
||||
// Returns the Cartesian Product of two sets.
|
||||
CartesianProduct(other Set) Set
|
||||
|
||||
// Returns the members of the set as a slice.
|
||||
ToSlice() []interface{}
|
||||
}
|
||||
|
||||
// NewSet creates and returns a reference to an empty set. Operations
|
||||
// on the resulting set are thread-safe.
|
||||
func NewSet(s ...interface{}) Set {
|
||||
set := newThreadSafeSet()
|
||||
for _, item := range s {
|
||||
set.Add(item)
|
||||
}
|
||||
return &set
|
||||
}
|
||||
|
||||
// NewSetWith creates and returns a new set with the given elements.
|
||||
// Operations on the resulting set are thread-safe.
|
||||
func NewSetWith(elts ...interface{}) Set {
|
||||
return NewSetFromSlice(elts)
|
||||
}
|
||||
|
||||
// NewSetFromSlice creates and returns a reference to a set from an
|
||||
// existing slice. Operations on the resulting set are thread-safe.
|
||||
func NewSetFromSlice(s []interface{}) Set {
|
||||
a := NewSet(s...)
|
||||
return a
|
||||
}
|
||||
|
||||
// NewThreadUnsafeSet creates and returns a reference to an empty set.
|
||||
// Operations on the resulting set are not thread-safe.
|
||||
func NewThreadUnsafeSet() Set {
|
||||
set := newThreadUnsafeSet()
|
||||
return &set
|
||||
}
|
||||
|
||||
// NewThreadUnsafeSetFromSlice creates and returns a reference to a
|
||||
// set from an existing slice. Operations on the resulting set are
|
||||
// not thread-safe.
|
||||
func NewThreadUnsafeSetFromSlice(s []interface{}) Set {
|
||||
a := NewThreadUnsafeSet()
|
||||
for _, item := range s {
|
||||
a.Add(item)
|
||||
}
|
||||
return a
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,283 @@
|
||||
/*
|
||||
Open Source Initiative OSI - The MIT License (MIT):Licensing
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package mapset
|
||||
|
||||
import "sync"
|
||||
|
||||
type threadSafeSet struct {
|
||||
s threadUnsafeSet
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func newThreadSafeSet() threadSafeSet {
|
||||
return threadSafeSet{s: newThreadUnsafeSet()}
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Add(i interface{}) bool {
|
||||
set.Lock()
|
||||
ret := set.s.Add(i)
|
||||
set.Unlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Contains(i ...interface{}) bool {
|
||||
set.RLock()
|
||||
ret := set.s.Contains(i...)
|
||||
set.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) IsSubset(other Set) bool {
|
||||
o := other.(*threadSafeSet)
|
||||
|
||||
set.RLock()
|
||||
o.RLock()
|
||||
|
||||
ret := set.s.IsSubset(&o.s)
|
||||
set.RUnlock()
|
||||
o.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) IsProperSubset(other Set) bool {
|
||||
o := other.(*threadSafeSet)
|
||||
|
||||
set.RLock()
|
||||
defer set.RUnlock()
|
||||
o.RLock()
|
||||
defer o.RUnlock()
|
||||
|
||||
return set.s.IsProperSubset(&o.s)
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) IsSuperset(other Set) bool {
|
||||
return other.IsSubset(set)
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) IsProperSuperset(other Set) bool {
|
||||
return other.IsProperSubset(set)
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Union(other Set) Set {
|
||||
o := other.(*threadSafeSet)
|
||||
|
||||
set.RLock()
|
||||
o.RLock()
|
||||
|
||||
unsafeUnion := set.s.Union(&o.s).(*threadUnsafeSet)
|
||||
ret := &threadSafeSet{s: *unsafeUnion}
|
||||
set.RUnlock()
|
||||
o.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Intersect(other Set) Set {
|
||||
o := other.(*threadSafeSet)
|
||||
|
||||
set.RLock()
|
||||
o.RLock()
|
||||
|
||||
unsafeIntersection := set.s.Intersect(&o.s).(*threadUnsafeSet)
|
||||
ret := &threadSafeSet{s: *unsafeIntersection}
|
||||
set.RUnlock()
|
||||
o.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Difference(other Set) Set {
|
||||
o := other.(*threadSafeSet)
|
||||
|
||||
set.RLock()
|
||||
o.RLock()
|
||||
|
||||
unsafeDifference := set.s.Difference(&o.s).(*threadUnsafeSet)
|
||||
ret := &threadSafeSet{s: *unsafeDifference}
|
||||
set.RUnlock()
|
||||
o.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) SymmetricDifference(other Set) Set {
|
||||
o := other.(*threadSafeSet)
|
||||
|
||||
set.RLock()
|
||||
o.RLock()
|
||||
|
||||
unsafeDifference := set.s.SymmetricDifference(&o.s).(*threadUnsafeSet)
|
||||
ret := &threadSafeSet{s: *unsafeDifference}
|
||||
set.RUnlock()
|
||||
o.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Clear() {
|
||||
set.Lock()
|
||||
set.s = newThreadUnsafeSet()
|
||||
set.Unlock()
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Remove(i interface{}) {
|
||||
set.Lock()
|
||||
delete(set.s, i)
|
||||
set.Unlock()
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Cardinality() int {
|
||||
set.RLock()
|
||||
defer set.RUnlock()
|
||||
return len(set.s)
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Each(cb func(interface{}) bool) {
|
||||
set.RLock()
|
||||
for elem := range set.s {
|
||||
if cb(elem) {
|
||||
break
|
||||
}
|
||||
}
|
||||
set.RUnlock()
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Iter() <-chan interface{} {
|
||||
ch := make(chan interface{})
|
||||
go func() {
|
||||
set.RLock()
|
||||
|
||||
for elem := range set.s {
|
||||
ch <- elem
|
||||
}
|
||||
close(ch)
|
||||
set.RUnlock()
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Iterator() *Iterator {
|
||||
iterator, ch, stopCh := newIterator()
|
||||
|
||||
go func() {
|
||||
set.RLock()
|
||||
L:
|
||||
for elem := range set.s {
|
||||
select {
|
||||
case <-stopCh:
|
||||
break L
|
||||
case ch <- elem:
|
||||
}
|
||||
}
|
||||
close(ch)
|
||||
set.RUnlock()
|
||||
}()
|
||||
|
||||
return iterator
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Equal(other Set) bool {
|
||||
o := other.(*threadSafeSet)
|
||||
|
||||
set.RLock()
|
||||
o.RLock()
|
||||
|
||||
ret := set.s.Equal(&o.s)
|
||||
set.RUnlock()
|
||||
o.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Clone() Set {
|
||||
set.RLock()
|
||||
|
||||
unsafeClone := set.s.Clone().(*threadUnsafeSet)
|
||||
ret := &threadSafeSet{s: *unsafeClone}
|
||||
set.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) String() string {
|
||||
set.RLock()
|
||||
ret := set.s.String()
|
||||
set.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) PowerSet() Set {
|
||||
set.RLock()
|
||||
unsafePowerSet := set.s.PowerSet().(*threadUnsafeSet)
|
||||
set.RUnlock()
|
||||
|
||||
ret := &threadSafeSet{s: newThreadUnsafeSet()}
|
||||
for subset := range unsafePowerSet.Iter() {
|
||||
unsafeSubset := subset.(*threadUnsafeSet)
|
||||
ret.Add(&threadSafeSet{s: *unsafeSubset})
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Pop() interface{} {
|
||||
set.Lock()
|
||||
defer set.Unlock()
|
||||
return set.s.Pop()
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) CartesianProduct(other Set) Set {
|
||||
o := other.(*threadSafeSet)
|
||||
|
||||
set.RLock()
|
||||
o.RLock()
|
||||
|
||||
unsafeCartProduct := set.s.CartesianProduct(&o.s).(*threadUnsafeSet)
|
||||
ret := &threadSafeSet{s: *unsafeCartProduct}
|
||||
set.RUnlock()
|
||||
o.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) ToSlice() []interface{} {
|
||||
keys := make([]interface{}, 0, set.Cardinality())
|
||||
set.RLock()
|
||||
for elem := range set.s {
|
||||
keys = append(keys, elem)
|
||||
}
|
||||
set.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) MarshalJSON() ([]byte, error) {
|
||||
set.RLock()
|
||||
b, err := set.s.MarshalJSON()
|
||||
set.RUnlock()
|
||||
|
||||
return b, err
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) UnmarshalJSON(p []byte) error {
|
||||
set.RLock()
|
||||
err := set.s.UnmarshalJSON(p)
|
||||
set.RUnlock()
|
||||
|
||||
return err
|
||||
}
|
@ -0,0 +1,524 @@
|
||||
/*
|
||||
Open Source Initiative OSI - The MIT License (MIT):Licensing
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package mapset
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const N = 1000
|
||||
|
||||
func Test_AddConcurrent(t *testing.T) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
|
||||
s := NewSet()
|
||||
ints := rand.Perm(N)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(ints))
|
||||
for i := 0; i < len(ints); i++ {
|
||||
go func(i int) {
|
||||
s.Add(i)
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
for _, i := range ints {
|
||||
if !s.Contains(i) {
|
||||
t.Errorf("Set is missing element: %v", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_CardinalityConcurrent(t *testing.T) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
|
||||
s := NewSet()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
elems := s.Cardinality()
|
||||
for i := 0; i < N; i++ {
|
||||
newElems := s.Cardinality()
|
||||
if newElems < elems {
|
||||
t.Errorf("Cardinality shrunk from %v to %v", elems, newElems)
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
for i := 0; i < N; i++ {
|
||||
s.Add(rand.Int())
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func Test_ClearConcurrent(t *testing.T) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
|
||||
s := NewSet()
|
||||
ints := rand.Perm(N)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(ints))
|
||||
for i := 0; i < len(ints); i++ {
|
||||
go func() {
|
||||
s.Clear()
|
||||
wg.Done()
|
||||
}()
|
||||
go func(i int) {
|
||||
s.Add(i)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func Test_CloneConcurrent(t *testing.T) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
|
||||
s := NewSet()
|
||||
ints := rand.Perm(N)
|
||||
|
||||
for _, v := range ints {
|
||||
s.Add(v)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(ints))
|
||||
for i := range ints {
|
||||
go func(i int) {
|
||||
s.Remove(i)
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
|
||||
s.Clone()
|
||||
}
|
||||
|
||||
func Test_ContainsConcurrent(t *testing.T) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
|
||||
s := NewSet()
|
||||
ints := rand.Perm(N)
|
||||
interfaces := make([]interface{}, 0)
|
||||
for _, v := range ints {
|
||||
s.Add(v)
|
||||
interfaces = append(interfaces, v)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for range ints {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
s.Contains(interfaces...)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func Test_DifferenceConcurrent(t *testing.T) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
|
||||
s, ss := NewSet(), NewSet()
|
||||
ints := rand.Perm(N)
|
||||
for _, v := range ints {
|
||||
s.Add(v)
|
||||
ss.Add(v)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for range ints {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
s.Difference(ss)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func Test_EqualConcurrent(t *testing.T) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
|
||||
s, ss := NewSet(), NewSet()
|
||||
ints := rand.Perm(N)
|
||||
for _, v := range ints {
|
||||
s.Add(v)
|
||||
ss.Add(v)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for range ints {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
s.Equal(ss)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func Test_IntersectConcurrent(t *testing.T) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
|
||||
s, ss := NewSet(), NewSet()
|
||||
ints := rand.Perm(N)
|
||||
for _, v := range ints {
|
||||
s.Add(v)
|
||||
ss.Add(v)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for range ints {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
s.Intersect(ss)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func Test_IsSubsetConcurrent(t *testing.T) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
|
||||
s, ss := NewSet(), NewSet()
|
||||
ints := rand.Perm(N)
|
||||
for _, v := range ints {
|
||||
s.Add(v)
|
||||
ss.Add(v)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for range ints {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
s.IsSubset(ss)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func Test_IsProperSubsetConcurrent(t *testing.T) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
|
||||
s, ss := NewSet(), NewSet()
|
||||
ints := rand.Perm(N)
|
||||
for _, v := range ints {
|
||||
s.Add(v)
|
||||
ss.Add(v)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for range ints {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
s.IsProperSubset(ss)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func Test_IsSupersetConcurrent(t *testing.T) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
|
||||
s, ss := NewSet(), NewSet()
|
||||
ints := rand.Perm(N)
|
||||
for _, v := range ints {
|
||||
s.Add(v)
|
||||
ss.Add(v)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for range ints {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
s.IsSuperset(ss)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func Test_IsProperSupersetConcurrent(t *testing.T) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
|
||||
s, ss := NewSet(), NewSet()
|
||||
ints := rand.Perm(N)
|
||||
for _, v := range ints {
|
||||
s.Add(v)
|
||||
ss.Add(v)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for range ints {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
s.IsProperSuperset(ss)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func Test_EachConcurrent(t *testing.T) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
concurrent := 10
|
||||
|
||||
s := NewSet()
|
||||
ints := rand.Perm(N)
|
||||
for _, v := range ints {
|
||||
s.Add(v)
|
||||
}
|
||||
|
||||
var count int64
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(concurrent)
|
||||
for n := 0; n < concurrent; n++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
s.Each(func(elem interface{}) bool {
|
||||
atomic.AddInt64(&count, 1)
|
||||
return false
|
||||
})
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if count != int64(N*concurrent) {
|
||||
t.Errorf("%v != %v", count, int64(N*concurrent))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_IterConcurrent(t *testing.T) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
|
||||
s := NewSet()
|
||||
ints := rand.Perm(N)
|
||||
for _, v := range ints {
|
||||
s.Add(v)
|
||||
}
|
||||
|
||||
cs := make([]<-chan interface{}, 0)
|
||||
for range ints {
|
||||
cs = append(cs, s.Iter())
|
||||
}
|
||||
|
||||
c := make(chan interface{})
|
||||
go func() {
|
||||
for n := 0; n < len(ints)*N; {
|
||||
for _, d := range cs {
|
||||
select {
|
||||
case <-d:
|
||||
n++
|
||||
c <- nil
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
close(c)
|
||||
}()
|
||||
|
||||
for range c {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_RemoveConcurrent(t *testing.T) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
|
||||
s := NewSet()
|
||||
ints := rand.Perm(N)
|
||||
for _, v := range ints {
|
||||
s.Add(v)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(ints))
|
||||
for _, v := range ints {
|
||||
go func(i int) {
|
||||
s.Remove(i)
|
||||
wg.Done()
|
||||
}(v)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if s.Cardinality() != 0 {
|
||||
t.Errorf("Expected cardinality 0; got %v", s.Cardinality())
|
||||
}
|
||||
}
|
||||
|
||||
func Test_StringConcurrent(t *testing.T) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
|
||||
s := NewSet()
|
||||
ints := rand.Perm(N)
|
||||
for _, v := range ints {
|
||||
s.Add(v)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(ints))
|
||||
for range ints {
|
||||
go func() {
|
||||
_ = s.String()
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func Test_SymmetricDifferenceConcurrent(t *testing.T) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
|
||||
s, ss := NewSet(), NewSet()
|
||||
ints := rand.Perm(N)
|
||||
for _, v := range ints {
|
||||
s.Add(v)
|
||||
ss.Add(v)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for range ints {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
s.SymmetricDifference(ss)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func Test_ToSlice(t *testing.T) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
|
||||
s := NewSet()
|
||||
ints := rand.Perm(N)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(ints))
|
||||
for i := 0; i < len(ints); i++ {
|
||||
go func(i int) {
|
||||
s.Add(i)
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
setAsSlice := s.ToSlice()
|
||||
if len(setAsSlice) != s.Cardinality() {
|
||||
t.Errorf("Set length is incorrect: %v", len(setAsSlice))
|
||||
}
|
||||
|
||||
for _, i := range setAsSlice {
|
||||
if !s.Contains(i) {
|
||||
t.Errorf("Set is missing element: %v", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test_ToSliceDeadlock - fixes issue: https://github.com/deckarep/golang-set/issues/36
|
||||
// This code reveals the deadlock however it doesn't happen consistently.
|
||||
func Test_ToSliceDeadlock(t *testing.T) {
|
||||
runtime.GOMAXPROCS(2)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
set := NewSet()
|
||||
workers := 10
|
||||
wg.Add(workers)
|
||||
for i := 1; i <= workers; i++ {
|
||||
go func() {
|
||||
for j := 0; j < 1000; j++ {
|
||||
set.Add(1)
|
||||
set.ToSlice()
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func Test_UnmarshalJSON(t *testing.T) {
|
||||
s := []byte(`["test", 1, 2, 3, ["4,5,6"]]`)
|
||||
expected := NewSetFromSlice(
|
||||
[]interface{}{
|
||||
json.Number("1"),
|
||||
json.Number("2"),
|
||||
json.Number("3"),
|
||||
"test",
|
||||
},
|
||||
)
|
||||
actual := NewSet()
|
||||
err := json.Unmarshal(s, actual)
|
||||
if err != nil {
|
||||
t.Errorf("Error should be nil: %v", err)
|
||||
}
|
||||
|
||||
if !expected.Equal(actual) {
|
||||
t.Errorf("Expected no difference, got: %v", expected.Difference(actual))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MarshalJSON(t *testing.T) {
|
||||
expected := NewSetFromSlice(
|
||||
[]interface{}{
|
||||
json.Number("1"),
|
||||
"test",
|
||||
},
|
||||
)
|
||||
|
||||
b, err := json.Marshal(
|
||||
NewSetFromSlice(
|
||||
[]interface{}{
|
||||
1,
|
||||
"test",
|
||||
},
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Errorf("Error should be nil: %v", err)
|
||||
}
|
||||
|
||||
actual := NewSet()
|
||||
err = json.Unmarshal(b, actual)
|
||||
if err != nil {
|
||||
t.Errorf("Error should be nil: %v", err)
|
||||
}
|
||||
|
||||
if !expected.Equal(actual) {
|
||||
t.Errorf("Expected no difference, got: %v", expected.Difference(actual))
|
||||
}
|
||||
}
|
@ -0,0 +1,337 @@
|
||||
/*
|
||||
Open Source Initiative OSI - The MIT License (MIT):Licensing
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package mapset
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type threadUnsafeSet map[interface{}]struct{}
|
||||
|
||||
// An OrderedPair represents a 2-tuple of values.
|
||||
type OrderedPair struct {
|
||||
First interface{}
|
||||
Second interface{}
|
||||
}
|
||||
|
||||
func newThreadUnsafeSet() threadUnsafeSet {
|
||||
return make(threadUnsafeSet)
|
||||
}
|
||||
|
||||
// Equal says whether two 2-tuples contain the same values in the same order.
|
||||
func (pair *OrderedPair) Equal(other OrderedPair) bool {
|
||||
if pair.First == other.First &&
|
||||
pair.Second == other.Second {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Add(i interface{}) bool {
|
||||
_, found := (*set)[i]
|
||||
if found {
|
||||
return false //False if it existed already
|
||||
}
|
||||
|
||||
(*set)[i] = struct{}{}
|
||||
return true
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Contains(i ...interface{}) bool {
|
||||
for _, val := range i {
|
||||
if _, ok := (*set)[val]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) IsSubset(other Set) bool {
|
||||
_ = other.(*threadUnsafeSet)
|
||||
for elem := range *set {
|
||||
if !other.Contains(elem) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) IsProperSubset(other Set) bool {
|
||||
return set.IsSubset(other) && !set.Equal(other)
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) IsSuperset(other Set) bool {
|
||||
return other.IsSubset(set)
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) IsProperSuperset(other Set) bool {
|
||||
return set.IsSuperset(other) && !set.Equal(other)
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Union(other Set) Set {
|
||||
o := other.(*threadUnsafeSet)
|
||||
|
||||
unionedSet := newThreadUnsafeSet()
|
||||
|
||||
for elem := range *set {
|
||||
unionedSet.Add(elem)
|
||||
}
|
||||
for elem := range *o {
|
||||
unionedSet.Add(elem)
|
||||
}
|
||||
return &unionedSet
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Intersect(other Set) Set {
|
||||
o := other.(*threadUnsafeSet)
|
||||
|
||||
intersection := newThreadUnsafeSet()
|
||||
// loop over smaller set
|
||||
if set.Cardinality() < other.Cardinality() {
|
||||
for elem := range *set {
|
||||
if other.Contains(elem) {
|
||||
intersection.Add(elem)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for elem := range *o {
|
||||
if set.Contains(elem) {
|
||||
intersection.Add(elem)
|
||||
}
|
||||
}
|
||||
}
|
||||
return &intersection
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Difference(other Set) Set {
|
||||
_ = other.(*threadUnsafeSet)
|
||||
|
||||
difference := newThreadUnsafeSet()
|
||||
for elem := range *set {
|
||||
if !other.Contains(elem) {
|
||||
difference.Add(elem)
|
||||
}
|
||||
}
|
||||
return &difference
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) SymmetricDifference(other Set) Set {
|
||||
_ = other.(*threadUnsafeSet)
|
||||
|
||||
aDiff := set.Difference(other)
|
||||
bDiff := other.Difference(set)
|
||||
return aDiff.Union(bDiff)
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Clear() {
|
||||
*set = newThreadUnsafeSet()
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Remove(i interface{}) {
|
||||
delete(*set, i)
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Cardinality() int {
|
||||
return len(*set)
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Each(cb func(interface{}) bool) {
|
||||
for elem := range *set {
|
||||
if cb(elem) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Iter() <-chan interface{} {
|
||||
ch := make(chan interface{})
|
||||
go func() {
|
||||
for elem := range *set {
|
||||
ch <- elem
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Iterator() *Iterator {
|
||||
iterator, ch, stopCh := newIterator()
|
||||
|
||||
go func() {
|
||||
L:
|
||||
for elem := range *set {
|
||||
select {
|
||||
case <-stopCh:
|
||||
break L
|
||||
case ch <- elem:
|
||||
}
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
return iterator
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Equal(other Set) bool {
|
||||
_ = other.(*threadUnsafeSet)
|
||||
|
||||
if set.Cardinality() != other.Cardinality() {
|
||||
return false
|
||||
}
|
||||
for elem := range *set {
|
||||
if !other.Contains(elem) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Clone() Set {
|
||||
clonedSet := newThreadUnsafeSet()
|
||||
for elem := range *set {
|
||||
clonedSet.Add(elem)
|
||||
}
|
||||
return &clonedSet
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) String() string {
|
||||
items := make([]string, 0, len(*set))
|
||||
|
||||
for elem := range *set {
|
||||
items = append(items, fmt.Sprintf("%v", elem))
|
||||
}
|
||||
return fmt.Sprintf("Set{%s}", strings.Join(items, ", "))
|
||||
}
|
||||
|
||||
// String outputs a 2-tuple in the form "(A, B)".
|
||||
func (pair OrderedPair) String() string {
|
||||
return fmt.Sprintf("(%v, %v)", pair.First, pair.Second)
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Pop() interface{} {
|
||||
for item := range *set {
|
||||
delete(*set, item)
|
||||
return item
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) PowerSet() Set {
|
||||
powSet := NewThreadUnsafeSet()
|
||||
nullset := newThreadUnsafeSet()
|
||||
powSet.Add(&nullset)
|
||||
|
||||
for es := range *set {
|
||||
u := newThreadUnsafeSet()
|
||||
j := powSet.Iter()
|
||||
for er := range j {
|
||||
p := newThreadUnsafeSet()
|
||||
if reflect.TypeOf(er).Name() == "" {
|
||||
k := er.(*threadUnsafeSet)
|
||||
for ek := range *(k) {
|
||||
p.Add(ek)
|
||||
}
|
||||
} else {
|
||||
p.Add(er)
|
||||
}
|
||||
p.Add(es)
|
||||
u.Add(&p)
|
||||
}
|
||||
|
||||
powSet = powSet.Union(&u)
|
||||
}
|
||||
|
||||
return powSet
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) CartesianProduct(other Set) Set {
|
||||
o := other.(*threadUnsafeSet)
|
||||
cartProduct := NewThreadUnsafeSet()
|
||||
|
||||
for i := range *set {
|
||||
for j := range *o {
|
||||
elem := OrderedPair{First: i, Second: j}
|
||||
cartProduct.Add(elem)
|
||||
}
|
||||
}
|
||||
|
||||
return cartProduct
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) ToSlice() []interface{} {
|
||||
keys := make([]interface{}, 0, set.Cardinality())
|
||||
for elem := range *set {
|
||||
keys = append(keys, elem)
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
// MarshalJSON creates a JSON array from the set, it marshals all elements
|
||||
func (set *threadUnsafeSet) MarshalJSON() ([]byte, error) {
|
||||
items := make([]string, 0, set.Cardinality())
|
||||
|
||||
for elem := range *set {
|
||||
b, err := json.Marshal(elem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items = append(items, string(b))
|
||||
}
|
||||
|
||||
return []byte(fmt.Sprintf("[%s]", strings.Join(items, ","))), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON recreates a set from a JSON array, it only decodes
|
||||
// primitive types. Numbers are decoded as json.Number.
|
||||
func (set *threadUnsafeSet) UnmarshalJSON(b []byte) error {
|
||||
var i []interface{}
|
||||
|
||||
d := json.NewDecoder(bytes.NewReader(b))
|
||||
d.UseNumber()
|
||||
err := d.Decode(&i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range i {
|
||||
switch t := v.(type) {
|
||||
case []interface{}, map[string]interface{}:
|
||||
continue
|
||||
default:
|
||||
set.Add(t)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Reference in new issue