github.com/abayer/test-infra@v0.0.5/velodrome/transform/plugins/states.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package plugins 18 19 import ( 20 "strings" 21 "time" 22 ) 23 24 // State describes a pull-request states, based on the events we've 25 // seen. 26 type State interface { 27 // Has the state been activated 28 Active() bool 29 // How long has the state been activated (will panic if not active) 30 Age(t time.Time) time.Duration 31 // Receive the event, return the new state 32 ReceiveEvent(eventName, label string, t time.Time) (State, bool) 33 } 34 35 // ActiveState describe a states that has been enabled. 36 type ActiveState struct { 37 startTime time.Time 38 exit EventMatcher 39 } 40 41 var _ State = &ActiveState{} 42 43 // Active if always true for an ActiveState 44 func (ActiveState) Active() bool { 45 return true 46 } 47 48 // Age gives the time since the state has been activated. 49 func (a *ActiveState) Age(t time.Time) time.Duration { 50 return t.Sub(a.startTime) 51 } 52 53 // ReceiveEvent checks if the event matches the exit criteria. 54 // Returns a new InactiveState or self, and true if it changed. 55 func (a *ActiveState) ReceiveEvent(eventName, label string, t time.Time) (State, bool) { 56 if a.exit.Match(eventName, label) { 57 return &InactiveState{ 58 entry: a.exit.Opposite(), 59 }, true 60 } 61 return a, false 62 } 63 64 // InactiveState describes a state that has not enabled, or been disabled. 65 type InactiveState struct { 66 entry EventMatcher 67 } 68 69 var _ State = &InactiveState{} 70 71 // Active is always false for an InactiveState 72 func (InactiveState) Active() bool { 73 return false 74 } 75 76 // Age doesn't make sense for InactiveState. 77 func (i *InactiveState) Age(t time.Time) time.Duration { 78 panic("InactiveState doesn't have an age.") 79 } 80 81 // ReceiveEvent checks if the event matches the entry criteria 82 // Returns a new ActiveState or self, and true if it changed. 83 func (i *InactiveState) ReceiveEvent(eventName, label string, t time.Time) (State, bool) { 84 if i.entry.Match(eventName, label) { 85 return &ActiveState{ 86 startTime: t, 87 exit: i.entry.Opposite(), 88 }, true 89 } 90 return i, false 91 } 92 93 // MultiState tracks multiple individual states at the same time. 94 type MultiState struct { 95 states []State 96 } 97 98 var _ State = &MultiState{} 99 100 // Active is true if all the states are active. 101 func (m *MultiState) Active() bool { 102 for _, state := range m.states { 103 if !state.Active() { 104 return false 105 } 106 } 107 return true 108 } 109 110 // Age returns the time since all states have been activated. 111 // It will panic if any of the state is not active. 112 func (m *MultiState) Age(t time.Time) time.Duration { 113 minAge := time.Duration(1<<63 - 1) 114 for _, state := range m.states { 115 stateAge := state.Age(t) 116 if stateAge < minAge { 117 minAge = stateAge 118 } 119 } 120 return minAge 121 } 122 123 // ReceiveEvent will send the event to each individual state, and update 124 // them if they change. 125 func (m *MultiState) ReceiveEvent(eventName, label string, t time.Time) (State, bool) { 126 oneChanged := false 127 for i := range m.states { 128 state, changed := m.states[i].ReceiveEvent(eventName, label, t) 129 if changed { 130 oneChanged = true 131 } 132 m.states[i] = state 133 134 } 135 return m, oneChanged 136 } 137 138 // NewState creates a MultiState instance based on the statesDescription 139 // string. statesDescription is a comma separated list of 140 // events. Events can be prepended with "!" (bang) to say that the state 141 // will be activated only if this event doesn't happen (or is inverted). 142 func NewState(statesDescription string) State { 143 states := []State{} 144 145 if statesDescription == "" { 146 // Create an infinite inactive state 147 return &InactiveState{ 148 entry: FalseEvent{}, 149 } 150 } 151 152 splitDescription := strings.Split(statesDescription, ",") 153 for _, description := range splitDescription { 154 description = strings.TrimSpace(description) 155 if strings.HasPrefix(description, "!") { 156 states = append(states, &ActiveState{ 157 startTime: time.Time{}, 158 exit: NewEventMatcher(description[1:]), 159 }) 160 } else { 161 states = append(states, &InactiveState{ 162 entry: NewEventMatcher(description), 163 }) 164 } 165 } 166 167 return &MultiState{states: states} 168 }