-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for TestRail timespan type
Support TestRail's string-based timestamp types: - Allow converting values such as "1w 2d" to and from native time.Duration values - Add a unit test for marshaling and unmarshaling
- Loading branch information
Showing
2 changed files
with
163 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package testrail | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
"time" | ||
) | ||
|
||
// Timespan field type. | ||
// e.g. "1m" or "2m 30s" -- similar to time.Duration | ||
// | ||
// For a description, see: | ||
// http://docs.gurock.com/testrail-api2/reference-results | ||
|
||
type timespan struct { | ||
time.Duration | ||
} | ||
|
||
// Unmarshal TestRail timespan into a time.Duration. | ||
// Transform TestRail-specific formats into something time.ParseDuration understands: | ||
// "4h 5m 6s" => "4h5m6s" | ||
// "1d" => "8h" | ||
// "1w" => "40h" | ||
// "1d 2h" => "8h2h" | ||
// "1w 2d 3h" => "40h16h3h" | ||
func (tsp *timespan) UnmarshalJSON(data []byte) error { | ||
const ( | ||
// These are hardcoded in TestRail. | ||
// https://discuss.gurock.com/t/estimate-fields/900 | ||
daysPerWeek = 5 | ||
hoursPerDay = 8 | ||
) | ||
var ( | ||
err error | ||
span string | ||
) | ||
|
||
if err = json.Unmarshal(data, &span); err != nil { | ||
return err | ||
} | ||
|
||
if span == "" { | ||
return nil | ||
} | ||
|
||
var parts []string | ||
for _, p := range strings.Fields(span) { | ||
if len(p) < 2 { | ||
return fmt.Errorf("%q: sequence is too short", p) | ||
} | ||
amount, err := strconv.Atoi(p[:len(p)-1]) | ||
if err != nil { | ||
return fmt.Errorf("%q: cannot convert to int: %v", amount, err) | ||
} | ||
unit := p[len(p)-1] | ||
switch unit { | ||
case 'd': | ||
unit = 'h' | ||
amount *= hoursPerDay | ||
case 'w': | ||
unit = 'h' | ||
amount *= daysPerWeek * hoursPerDay | ||
} | ||
parts = append(parts, fmt.Sprintf("%v%c", amount, unit)) | ||
} | ||
|
||
tsp.Duration, err = time.ParseDuration(strings.Join(parts, "")) | ||
if err != nil { | ||
return fmt.Errorf("%q: %v", span, err) | ||
} | ||
return nil | ||
} | ||
|
||
func (tsp timespan) MarshalJSON() ([]byte, error) { | ||
d := tsp.Duration | ||
|
||
if d == 0 { | ||
return []byte(`null`), nil | ||
} | ||
|
||
h, d := d/time.Hour, d%time.Hour | ||
m, d := d/time.Minute, d%time.Minute | ||
s := d / time.Second | ||
|
||
return []byte(fmt.Sprintf(`"%dh %dm %ds"`, h, m, s)), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package testrail | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestTimespanUnmarshal(t *testing.T) { | ||
var testData = []struct{ json, stringDuration string }{ | ||
{`null`, "0s"}, | ||
{`"15s"`, "15s"}, | ||
{`"12m"`, "12m"}, | ||
{`"11h"`, "11h"}, | ||
{`"4h 5m 6s"`, "4h5m6s"}, | ||
{`"1d"`, "8h"}, | ||
{`"1w"`, "40h"}, | ||
{`"1d 2h"`, "8h2h"}, | ||
{`"1w 2d 3h"`, "40h16h3h"}, | ||
} | ||
|
||
for _, data := range testData { | ||
var results []Result | ||
js := []byte(fmt.Sprintf(`[{"elapsed":%v}]`, data.json)) | ||
if err := json.Unmarshal(js, &results); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
r := results[0] | ||
expected, err := time.ParseDuration(data.stringDuration) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if r.Elapsed.Duration != expected { | ||
t.Fatalf("Wrong duration: %v", r.Elapsed.Duration) | ||
} | ||
} | ||
} | ||
|
||
func TestTimespanMarshal(t *testing.T) { | ||
var testData = []struct{ json, stringDuration string }{ | ||
{`null`, "0s"}, | ||
{`"0h 0m 15s"`, "15s"}, | ||
{`"0h 12m 0s"`, "12m"}, | ||
{`"11h 0m 0s"`, "11h"}, | ||
{`"4h 5m 6s"`, "4h5m6s"}, | ||
{`"8h 0m 0s"`, "8h"}, | ||
{`"40h 0m 0s"`, "40h"}, | ||
{`"10h 0m 0s"`, "8h2h"}, | ||
{`"59h 0m 0s"`, "40h16h3h"}, | ||
} | ||
|
||
for _, td := range testData { | ||
duration, err := time.ParseDuration(td.stringDuration) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
result := Result{ | ||
Elapsed: timespan{ | ||
Duration: duration, | ||
}, | ||
} | ||
data, err := json.Marshal(result.Elapsed) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if string(data) != td.json { | ||
t.Fatalf("Wrong data: %v", string(data)) | ||
} | ||
} | ||
} |