Compare commits

..

No commits in common. "main" and "v0.2.0" have entirely different histories.
main ... v0.2.0

5 changed files with 323 additions and 286 deletions

View File

@ -1,8 +1,9 @@
# cc-units - A unit system for ClusterCockpit # ccUnits - A unit system for ClusterCockpit
When working with metrics, the problem comes up that they may use different unit name but have the same unit in fact. There are a lot of real world examples like 'kB' and 'Kbyte'. In [cc-metric-collector](https://github.com/ClusterCockpit/cc-metric-collector), the collectors read data from different sources which may use different units or the programmer specifies a unit for a metric by hand. The cc-units system is not comparable with the SI unit system. If you are looking for a package for the SI units, see [here](https://pkg.go.dev/github.com/gurre/si). When working with metrics, the problem comes up that they may use different unit name but have the same unit in fact. There are a lot of real world examples like 'kB' and 'Kbyte'. In CC Metric Collector, the Collectors read data from different sources which may use different units or the programmer specifies a unit for a metric by hand. The ccUnits system is not comparable with the SI unit system. If you are looking for a package for the SI units, see [here](https://pkg.go.dev/github.com/gurre/si).
In order to enable unit comparison and conversion, the ccUnits package provides some helpers: In order to enable unit comparison and conversion, the ccUnits package provides some helpers:
There are basically two important functions:
```go ```go
NewUnit(unit string) Unit // create a new unit from some string like 'GHz', 'Mbyte' or 'kevents/s' NewUnit(unit string) Unit // create a new unit from some string like 'GHz', 'Mbyte' or 'kevents/s'
func GetUnitUnitFactor(in Unit, out Unit) (func(value float64) float64, error) // Get conversion function between two units func GetUnitUnitFactor(in Unit, out Unit) (func(value float64) float64, error) // Get conversion function between two units

View File

@ -12,7 +12,7 @@ const (
TemperatureC TemperatureC
TemperatureF TemperatureF
Rotation Rotation
Frequency Hertz
Time Time
Watt Watt
Joule Joule
@ -22,113 +22,171 @@ const (
Events Events
) )
type MeasureData struct {
Long string
Short string
Regex string
}
// Different names and regex used for input and output
var InvalidMeasureLong string = "Invalid"
var InvalidMeasureShort string = "inval"
var MeasuresMap map[Measure]MeasureData = map[Measure]MeasureData{
Bytes: {
Long: "byte",
Short: "B",
Regex: "^([bB][yY]?[tT]?[eE]?[sS]?)",
},
Flops: {
Long: "Flops",
Short: "Flops",
Regex: "^([fF][lL]?[oO]?[pP]?[sS]?)",
},
Percentage: {
Long: "Percent",
Short: "%",
Regex: "^(%|[pP]ercent)",
},
TemperatureC: {
Long: "DegreeC",
Short: "degC",
Regex: "^(deg[Cc]|°[cC])",
},
TemperatureF: {
Long: "DegreeF",
Short: "degF",
Regex: "^(deg[fF]|°[fF])",
},
Rotation: {
Long: "RPM",
Short: "RPM",
Regex: "^([rR][pP][mM])",
},
Frequency: {
Long: "Hertz",
Short: "Hz",
Regex: "^([hH][eE]?[rR]?[tT]?[zZ])",
},
Time: {
Long: "Seconds",
Short: "s",
Regex: "^([sS][eE]?[cC]?[oO]?[nN]?[dD]?[sS]?)",
},
Cycles: {
Long: "Cycles",
Short: "cyc",
Regex: "^([cC][yY][cC]?[lL]?[eE]?[sS]?)",
},
Watt: {
Long: "Watts",
Short: "W",
Regex: "^([wW][aA]?[tT]?[tT]?[sS]?)",
},
Joule: {
Long: "Joules",
Short: "J",
Regex: "^([jJ][oO]?[uU]?[lL]?[eE]?[sS]?)",
},
Requests: {
Long: "Requests",
Short: "requests",
Regex: "^([rR][eE][qQ][uU]?[eE]?[sS]?[tT]?[sS]?)",
},
Packets: {
Long: "Packets",
Short: "packets",
Regex: "^([pP][aA]?[cC]?[kK][eE]?[tT][sS]?)",
},
Events: {
Long: "Events",
Short: "events",
Regex: "^([eE][vV]?[eE]?[nN][tT][sS]?)",
},
}
// String returns the long string for the measure like 'Percent' or 'Seconds' // String returns the long string for the measure like 'Percent' or 'Seconds'
func (m *Measure) String() string { func (m *Measure) String() string {
if data, ok := MeasuresMap[*m]; ok { switch *m {
return data.Long case Bytes:
return "byte"
case Flops:
return "Flops"
case Percentage:
return "Percent"
case TemperatureC:
return "DegreeC"
case TemperatureF:
return "DegreeF"
case Rotation:
return "RPM"
case Hertz:
return "Hertz"
case Time:
return "Seconds"
case Watt:
return "Watts"
case Joule:
return "Joules"
case Cycles:
return "Cycles"
case Requests:
return "Requests"
case Packets:
return "Packets"
case Events:
return "Events"
case InvalidMeasure:
return "Invalid"
default:
return "Unknown"
} }
return InvalidMeasureLong
} }
// Short returns the short string for the measure like 'B' (Bytes), 's' (Time) or 'W' (Watt). Is is recommened to use Short() over String(). // Short returns the short string for the measure like 'B' (Bytes), 's' (Time) or 'W' (Watt). Is is recommened to use Short() over String().
func (m *Measure) Short() string { func (m *Measure) Short() string {
if data, ok := MeasuresMap[*m]; ok { switch *m {
return data.Short case Bytes:
return "B"
case Flops:
return "Flops"
case Percentage:
return "Percent"
case TemperatureC:
return "degC"
case TemperatureF:
return "degF"
case Rotation:
return "RPM"
case Hertz:
return "Hz"
case Time:
return "s"
case Watt:
return "W"
case Joule:
return "J"
case Cycles:
return "cyc"
case Requests:
return "requests"
case Packets:
return "packets"
case Events:
return "events"
case InvalidMeasure:
return "Invalid"
default:
return "Unknown"
} }
return InvalidMeasureShort
} }
const bytesRegexStr = `^([bB][yY]?[tT]?[eE]?[sS]?)`
const flopsRegexStr = `^([fF][lL]?[oO]?[pP]?[sS]?)`
const percentRegexStr = `^(%|[pP]ercent)`
const degreeCRegexStr = `^(deg[Cc]|°[cC])`
const degreeFRegexStr = `^(deg[fF]|°[fF])`
const rpmRegexStr = `^([rR][pP][mM])`
const hertzRegexStr = `^([hH][eE]?[rR]?[tT]?[zZ])`
const timeRegexStr = `^([sS][eE]?[cC]?[oO]?[nN]?[dD]?[sS]?)`
const wattRegexStr = `^([wW][aA]?[tT]?[tT]?[sS]?)`
const jouleRegexStr = `^([jJ][oO]?[uU]?[lL]?[eE]?[sS]?)`
const cyclesRegexStr = `^([cC][yY][cC]?[lL]?[eE]?[sS]?)`
const requestsRegexStr = `^([rR][eE][qQ][uU]?[eE]?[sS]?[tT]?[sS]?)`
const packetsRegexStr = `^([pP][aA]?[cC]?[kK][eE]?[tT][sS]?)`
const eventsRegexStr = `^([eE][vV]?[eE]?[nN][tT][sS]?)`
var bytesRegex = regexp.MustCompile(bytesRegexStr)
var flopsRegex = regexp.MustCompile(flopsRegexStr)
var percentRegex = regexp.MustCompile(percentRegexStr)
var degreeCRegex = regexp.MustCompile(degreeCRegexStr)
var degreeFRegex = regexp.MustCompile(degreeFRegexStr)
var rpmRegex = regexp.MustCompile(rpmRegexStr)
var hertzRegex = regexp.MustCompile(hertzRegexStr)
var timeRegex = regexp.MustCompile(timeRegexStr)
var wattRegex = regexp.MustCompile(wattRegexStr)
var jouleRegex = regexp.MustCompile(jouleRegexStr)
var cyclesRegex = regexp.MustCompile(cyclesRegexStr)
var requestsRegex = regexp.MustCompile(requestsRegexStr)
var packetsRegex = regexp.MustCompile(packetsRegexStr)
var eventsRegex = regexp.MustCompile(eventsRegexStr)
// NewMeasure creates a new measure out of a string representing a measure like 'Bytes', 'Flops' and 'precent'. // NewMeasure creates a new measure out of a string representing a measure like 'Bytes', 'Flops' and 'precent'.
// It uses regular expressions for matching. // It uses regular expressions for matching.
func NewMeasure(unit string) Measure { func NewMeasure(unit string) Measure {
for m, data := range MeasuresMap { var match []string
regex := regexp.MustCompile(data.Regex) match = bytesRegex.FindStringSubmatch(unit)
match := regex.FindStringSubmatch(unit)
if match != nil { if match != nil {
return m return Bytes
} }
match = flopsRegex.FindStringSubmatch(unit)
if match != nil {
return Flops
}
match = percentRegex.FindStringSubmatch(unit)
if match != nil {
return Percentage
}
match = degreeCRegex.FindStringSubmatch(unit)
if match != nil {
return TemperatureC
}
match = degreeFRegex.FindStringSubmatch(unit)
if match != nil {
return TemperatureF
}
match = rpmRegex.FindStringSubmatch(unit)
if match != nil {
return Rotation
}
match = hertzRegex.FindStringSubmatch(unit)
if match != nil {
return Hertz
}
match = timeRegex.FindStringSubmatch(unit)
if match != nil {
return Time
}
match = cyclesRegex.FindStringSubmatch(unit)
if match != nil {
return Cycles
}
match = wattRegex.FindStringSubmatch(unit)
if match != nil {
return Watt
}
match = jouleRegex.FindStringSubmatch(unit)
if match != nil {
return Joule
}
match = requestsRegex.FindStringSubmatch(unit)
if match != nil {
return Requests
}
match = packetsRegex.FindStringSubmatch(unit)
if match != nil {
return Packets
}
match = eventsRegex.FindStringSubmatch(unit)
if match != nil {
return Events
} }
return InvalidMeasure return InvalidMeasure
} }

View File

@ -1,8 +1,6 @@
package ccunits package ccunits
import ( import "regexp"
"regexp"
)
type Prefix float64 type Prefix float64
@ -29,146 +27,148 @@ const (
Zebi = 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 Zebi = 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024
Yobi = 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 Yobi = 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024
) )
const PrefixUnitSplitRegexStr = `^([kKmMgGtTpPeEzZyY]?[i]?)(.*)` const prefixRegexStr = `^([kKmMgGtTpP]?[i]?)(.*)`
var prefixUnitSplitRegex = regexp.MustCompile(PrefixUnitSplitRegexStr) var prefixRegex = regexp.MustCompile(prefixRegexStr)
type PrefixData struct {
Long string
Short string
Regex string
}
// Different names and regex used for input and output
var InvalidPrefixLong string = "Invalid"
var InvalidPrefixShort string = "inval"
var PrefixDataMap map[Prefix]PrefixData = map[Prefix]PrefixData{
Base: {
Long: "",
Short: "",
Regex: "^$",
},
Kilo: {
Long: "Kilo",
Short: "K",
Regex: "^[kK]$",
},
Mega: {
Long: "Mega",
Short: "M",
Regex: "^[M]$",
},
Giga: {
Long: "Giga",
Short: "G",
Regex: "^[gG]$",
},
Tera: {
Long: "Tera",
Short: "T",
Regex: "^[tT]$",
},
Peta: {
Long: "Peta",
Short: "P",
Regex: "^[pP]$",
},
Exa: {
Long: "Exa",
Short: "E",
Regex: "^[eE]$",
},
Zetta: {
Long: "Zetta",
Short: "Z",
Regex: "^[zZ]$",
},
Yotta: {
Long: "Yotta",
Short: "Y",
Regex: "^[yY]$",
},
Milli: {
Long: "Milli",
Short: "m",
Regex: "^[m]$",
},
Micro: {
Long: "Micro",
Short: "u",
Regex: "^[u]$",
},
Nano: {
Long: "Nano",
Short: "n",
Regex: "^[n]$",
},
Kibi: {
Long: "Kibi",
Short: "Ki",
Regex: "^[kK][i]$",
},
Mebi: {
Long: "Mebi",
Short: "Mi",
Regex: "^[M][i]$",
},
Gibi: {
Long: "Gibi",
Short: "Gi",
Regex: "^[gG][i]$",
},
Tebi: {
Long: "Tebi",
Short: "Ti",
Regex: "^[tT][i]$",
},
Pebi: {
Long: "Pebi",
Short: "Pi",
Regex: "^[pP][i]$",
},
Exbi: {
Long: "Exbi",
Short: "Ei",
Regex: "^[eE][i]$",
},
Zebi: {
Long: "Zebi",
Short: "Zi",
Regex: "^[zZ][i]$",
},
Yobi: {
Long: "Yobi",
Short: "Yi",
Regex: "^[yY][i]$",
},
}
// String returns the long string for the prefix like 'Kilo' or 'Mega' // String returns the long string for the prefix like 'Kilo' or 'Mega'
func (p *Prefix) String() string { func (s *Prefix) String() string {
if data, ok := PrefixDataMap[*p]; ok { switch *s {
return data.Long case InvalidPrefix:
return "Inval"
case Base:
return ""
case Kilo:
return "Kilo"
case Mega:
return "Mega"
case Giga:
return "Giga"
case Tera:
return "Tera"
case Peta:
return "Peta"
case Exa:
return "Exa"
case Zetta:
return "Zetta"
case Yotta:
return "Yotta"
case Milli:
return "Milli"
case Micro:
return "Micro"
case Nano:
return "Nano"
case Kibi:
return "Kibi"
case Mebi:
return "Mebi"
case Gibi:
return "Gibi"
case Tebi:
return "Tebi"
default:
return "Unkn"
} }
return InvalidMeasureLong
} }
// Prefix returns the short string for the prefix like 'K', 'M' or 'G'. Is is recommened to use Prefix() over String(). // Prefix returns the short string for the prefix like 'K', 'M' or 'G'. Is is recommened to use Prefix() over String().
func (p *Prefix) Prefix() string { func (s *Prefix) Prefix() string {
if data, ok := PrefixDataMap[*p]; ok { switch *s {
return data.Short case InvalidPrefix:
return "<inval>"
case Base:
return ""
case Kilo:
return "K"
case Mega:
return "M"
case Giga:
return "G"
case Tera:
return "T"
case Peta:
return "P"
case Exa:
return "E"
case Zetta:
return "Z"
case Yotta:
return "Y"
case Milli:
return "m"
case Micro:
return "u"
case Nano:
return "n"
case Kibi:
return "Ki"
case Mebi:
return "Mi"
case Gibi:
return "Gi"
case Tebi:
return "Ti"
default:
return "<unkn>"
} }
return InvalidMeasureShort
} }
// NewPrefix creates a new prefix out of a string representing a unit like 'k', 'K', 'M' or 'G'. // NewPrefix creates a new prefix out of a string representing a unit like 'k', 'K', 'M' or 'G'.
func NewPrefix(prefix string) Prefix { func NewPrefix(prefix string) Prefix {
for p, data := range PrefixDataMap { switch prefix {
regex := regexp.MustCompile(data.Regex) case "k":
match := regex.FindStringSubmatch(prefix) return Kilo
if match != nil { case "K":
return p return Kilo
} case "m":
} return Milli
case "M":
return Mega
case "g":
return Giga
case "G":
return Giga
case "t":
return Tera
case "T":
return Tera
case "p":
return Peta
case "P":
return Peta
case "e":
return Exa
case "E":
return Exa
case "z":
return Zetta
case "Z":
return Zetta
case "y":
return Yotta
case "Y":
return Yotta
case "u":
return Micro
case "n":
return Nano
case "ki":
return Kibi
case "Ki":
return Kibi
case "Mi":
return Mebi
case "gi":
return Gibi
case "Gi":
return Gibi
case "Ti":
return Tebi
case "":
return Base
default:
return InvalidPrefix return InvalidPrefix
} }
}

View File

@ -1,4 +1,3 @@
// Unit system for cluster monitoring metrics like bytes, flops and events
package ccunits package ccunits
import ( import (
@ -17,10 +16,10 @@ type Unit interface {
String() string String() string
Short() string Short() string
AddUnitDenominator(div Measure) AddUnitDenominator(div Measure)
GetPrefix() Prefix getPrefix() Prefix
GetMeasure() Measure getMeasure() Measure
GetUnitDenominator() Measure getUnitDenominator() Measure
SetPrefix(p Prefix) setPrefix(p Prefix)
} }
var INVALID_UNIT = NewUnit("foobar") var INVALID_UNIT = NewUnit("foobar")
@ -55,19 +54,19 @@ func (u *unit) AddUnitDenominator(div Measure) {
u.divMeasure = div u.divMeasure = div
} }
func (u *unit) GetPrefix() Prefix { func (u *unit) getPrefix() Prefix {
return u.prefix return u.prefix
} }
func (u *unit) SetPrefix(p Prefix) { func (u *unit) setPrefix(p Prefix) {
u.prefix = p u.prefix = p
} }
func (u *unit) GetMeasure() Measure { func (u *unit) getMeasure() Measure {
return u.measure return u.measure
} }
func (u *unit) GetUnitDenominator() Measure { func (u *unit) getUnitDenominator() Measure {
return u.divMeasure return u.divMeasure
} }
@ -162,8 +161,8 @@ func GetPrefixStringPrefixStringFactor(in string, out string) func(value interfa
func GetUnitPrefixFactor(in Unit, out Prefix) (func(value interface{}) interface{}, Unit) { func GetUnitPrefixFactor(in Unit, out Prefix) (func(value interface{}) interface{}, Unit) {
outUnit := NewUnit(in.Short()) outUnit := NewUnit(in.Short())
if outUnit.Valid() { if outUnit.Valid() {
outUnit.SetPrefix(out) outUnit.setPrefix(out)
conv := GetPrefixPrefixFactor(in.GetPrefix(), out) conv := GetPrefixPrefixFactor(in.getPrefix(), out)
return conv, outUnit return conv, outUnit
} }
return nil, INVALID_UNIT return nil, INVALID_UNIT
@ -187,14 +186,14 @@ func GetUnitStringPrefixStringFactor(in string, out string) (func(value interfac
// It is basically a wrapper for GetPrefixPrefixFactor with some special cases for temperature // It is basically a wrapper for GetPrefixPrefixFactor with some special cases for temperature
// conversion between Fahrenheit and Celsius. // conversion between Fahrenheit and Celsius.
func GetUnitUnitFactor(in Unit, out Unit) (func(value interface{}) interface{}, error) { func GetUnitUnitFactor(in Unit, out Unit) (func(value interface{}) interface{}, error) {
if in.GetMeasure() == TemperatureC && out.GetMeasure() == TemperatureF { if in.getMeasure() == TemperatureC && out.getMeasure() == TemperatureF {
return convertTempC2TempF, nil return convertTempC2TempF, nil
} else if in.GetMeasure() == TemperatureF && out.GetMeasure() == TemperatureC { } else if in.getMeasure() == TemperatureF && out.getMeasure() == TemperatureC {
return convertTempF2TempC, nil return convertTempF2TempC, nil
} else if in.GetMeasure() != out.GetMeasure() || in.GetUnitDenominator() != out.GetUnitDenominator() { } else if in.getMeasure() != out.getMeasure() || in.getUnitDenominator() != out.getUnitDenominator() {
return func(value interface{}) interface{} { return 1.0 }, fmt.Errorf("invalid measures in in and out Unit") return func(value interface{}) interface{} { return 1.0 }, fmt.Errorf("invalid measures in in and out Unit")
} }
return GetPrefixPrefixFactor(in.GetPrefix(), out.GetPrefix()), nil return GetPrefixPrefixFactor(in.getPrefix(), out.getPrefix()), nil
} }
// NewUnit creates a new unit out of a string representing a unit like 'Mbyte/s' or 'GHz'. // NewUnit creates a new unit out of a string representing a unit like 'Mbyte/s' or 'GHz'.
@ -205,7 +204,7 @@ func NewUnit(unitStr string) Unit {
measure: InvalidMeasure, measure: InvalidMeasure,
divMeasure: InvalidMeasure, divMeasure: InvalidMeasure,
} }
matches := prefixUnitSplitRegex.FindStringSubmatch(unitStr) matches := prefixRegex.FindStringSubmatch(unitStr)
if len(matches) > 2 { if len(matches) > 2 {
pre := NewPrefix(matches[1]) pre := NewPrefix(matches[1])
measures := strings.Split(matches[2], "/") measures := strings.Split(matches[2], "/")

View File

@ -2,7 +2,6 @@ package ccunits
import ( import (
"fmt" "fmt"
"regexp"
"testing" "testing"
) )
@ -67,7 +66,7 @@ func TestUnitsExact(t *testing.T) {
{"F/B", NewUnit("flops/Bytes")}, {"F/B", NewUnit("flops/Bytes")},
} }
compareUnitExact := func(in, out Unit) bool { compareUnitExact := func(in, out Unit) bool {
if in.GetMeasure() == out.GetMeasure() && in.GetUnitDenominator() == out.GetUnitDenominator() && in.GetPrefix() == out.GetPrefix() { if in.getMeasure() == out.getMeasure() && in.getUnitDenominator() == out.getUnitDenominator() && in.getPrefix() == out.getPrefix() {
return true return true
} }
return false return false
@ -76,8 +75,6 @@ func TestUnitsExact(t *testing.T) {
u := NewUnit(c.in) u := NewUnit(c.in)
if (!u.Valid()) || (!compareUnitExact(u, c.want)) { if (!u.Valid()) || (!compareUnitExact(u, c.want)) {
t.Errorf("func NewUnit(%q) == %q, want %q", c.in, u.String(), c.want.String()) t.Errorf("func NewUnit(%q) == %q, want %q", c.in, u.String(), c.want.String())
} else {
t.Logf("NewUnit(%q) == %q", c.in, u.String())
} }
} }
} }
@ -99,8 +96,8 @@ func TestUnitUnitConversion(t *testing.T) {
{"mb", NewUnit("MBytes"), 1.0}, {"mb", NewUnit("MBytes"), 1.0},
} }
compareUnitWithPrefix := func(in, out Unit, factor float64) bool { compareUnitWithPrefix := func(in, out Unit, factor float64) bool {
if in.GetMeasure() == out.GetMeasure() && in.GetUnitDenominator() == out.GetUnitDenominator() { if in.getMeasure() == out.getMeasure() && in.getUnitDenominator() == out.getUnitDenominator() {
if f := GetPrefixPrefixFactor(in.GetPrefix(), out.GetPrefix()); f(1.0) == factor { if f := GetPrefixPrefixFactor(in.getPrefix(), out.getPrefix()); f(1.0) == factor {
return true return true
} else { } else {
fmt.Println(f(1.0)) fmt.Println(f(1.0))
@ -111,9 +108,7 @@ func TestUnitUnitConversion(t *testing.T) {
for _, c := range testCases { for _, c := range testCases {
u := NewUnit(c.in) u := NewUnit(c.in)
if (!u.Valid()) || (!compareUnitWithPrefix(u, c.want, c.prefixFactor)) { if (!u.Valid()) || (!compareUnitWithPrefix(u, c.want, c.prefixFactor)) {
t.Errorf("GetPrefixPrefixFactor(%q, %q) invalid, want %q with factor %g", c.in, u.String(), c.want.String(), c.prefixFactor) t.Errorf("GetPrefixPrefixFactor(%q, %q) invalid, want %q with factor %f", c.in, u.String(), c.want.String(), c.prefixFactor)
} else {
t.Logf("GetPrefixPrefixFactor(%q, %q) = %g", c.in, c.want.String(), c.prefixFactor)
} }
} }
} }
@ -144,10 +139,9 @@ func TestUnitPrefixConversion(t *testing.T) {
u := NewUnit(c.in) u := NewUnit(c.in)
p := NewPrefix(c.want) p := NewPrefix(c.want)
if (!u.Valid()) || (!compareUnitPrefix(u, p, c.prefixFactor, c.wantUnit)) { if (!u.Valid()) || (!compareUnitPrefix(u, p, c.prefixFactor, c.wantUnit)) {
t.Errorf("GetUnitPrefixFactor(%q, %q) invalid, want %q with factor %g", c.in, p.Prefix(), c.wantUnit.String(), c.prefixFactor) t.Errorf("GetUnitPrefixFactor(%q, %q) invalid, want %q with factor %f", c.in, p.Prefix(), c.wantUnit.String(), c.prefixFactor)
} else {
t.Logf("GetUnitPrefixFactor(%q, %q) = %g", c.in, c.wantUnit.String(), c.prefixFactor)
} }
} }
} }
@ -163,39 +157,24 @@ func TestPrefixPrefixConversion(t *testing.T) {
{"", "M", 1e-6}, {"", "M", 1e-6},
{"", "m", 1e3}, {"", "m", 1e3},
{"m", "n", 1e6}, {"m", "n", 1e6},
//{"", "n", 1e9}, //does not work because of IEEE rounding problems //{"", "n", 1e9} does not work because of IEEE rounding problems
}
comparePrefixPrefix := func(in Prefix, out Prefix, factor float64) bool {
if in != InvalidPrefix && out != InvalidPrefix {
conv := GetPrefixPrefixFactor(in, out)
value := conv(1.0)
fmt.Println("1.0 -> ", value, ", want ", factor)
if value == factor {
return true
}
}
return false
} }
for _, c := range testCases { for _, c := range testCases {
i := NewPrefix(c.in) i := NewPrefix(c.in)
o := NewPrefix(c.want) o := NewPrefix(c.want)
if i != InvalidPrefix && o != InvalidPrefix { if !comparePrefixPrefix(i, o, c.prefixFactor) {
conv := GetPrefixPrefixFactor(i, o) t.Errorf("GetPrefixPrefixFactor(%q, %q) invalid, want %q with factor %f", c.in, c.want, o.Prefix(), c.prefixFactor)
value := conv(1.0)
if value != c.prefixFactor {
t.Errorf("GetPrefixPrefixFactor(%q, %q) invalid, want %q with factor %g but got %g", c.in, c.want, o.Prefix(), c.prefixFactor, value)
} else {
t.Logf("GetPrefixPrefixFactor(%q, %q) = %g", c.in, c.want, c.prefixFactor)
} }
} }
} }
}
func TestMeasureRegex(t *testing.T) {
for _, data := range MeasuresMap {
_, err := regexp.Compile(data.Regex)
if err != nil {
t.Errorf("failed to compile regex '%s': %s", data.Regex, err.Error())
}
t.Logf("succussfully compiled regex '%s' for measure %s", data.Regex, data.Long)
}
}
func TestPrefixRegex(t *testing.T) {
for _, data := range PrefixDataMap {
_, err := regexp.Compile(data.Regex)
if err != nil {
t.Errorf("failed to compile regex '%s': %s", data.Regex, err.Error())
}
t.Logf("succussfully compiled regex '%s' for prefix %s", data.Regex, data.Long)
}
}