diff --git a/src/clj/lambdacd/internal/default_pipeline_state.clj b/src/clj/lambdacd/internal/default_pipeline_state.clj index 5e198bd7..1dbd8c84 100644 --- a/src/clj/lambdacd/internal/default_pipeline_state.clj +++ b/src/clj/lambdacd/internal/default_pipeline_state.clj @@ -40,24 +40,32 @@ (defrecord DefaultPipelineState [state-atom structure-atom home-dir max-builds] old-pipeline-state/PipelineStateComponent (update [self build-number step-id new-step-result] - (if (not (nil? state-atom)) ; convenience for tests: if no state exists we just do nothing - (let [new-state (swap! state-atom #(->> % - (update-step-result-in-state build-number step-id new-step-result) - (truncate-build-history home-dir max-builds)))] - (persistence/write-build-history home-dir build-number new-state)))) + (protocols/consume-step-result-update self build-number step-id new-step-result)) (get-all [self] @state-atom) (get-internal-state [self] state-atom) (next-build-number [self] (inc (most-recent-build-number-in-state @state-atom))) + protocols/PipelineStructureConsumer (consume-pipeline-structure [self build-number pipeline-structure-representation] (swap! structure-atom #(assoc % build-number pipeline-structure-representation)) (persistence/write-pipeline-structure home-dir build-number pipeline-structure-representation)) protocols/PipelineStructureSource (get-pipeline-structure [self build-number] - (get @structure-atom build-number))) + (get @structure-atom build-number)) + + protocols/StepResultUpdateConsumer + (consume-step-result-update [self build-number step-id step-result] + (if (not (nil? state-atom)) ; convenience for tests: if no state exists we just do nothing + (let [new-state (swap! state-atom #(->> % + (update-step-result-in-state build-number step-id step-result) + (truncate-build-history home-dir max-builds)))] + (persistence/write-build-history home-dir build-number new-state)))) + protocols/QueryStepResultsSource + (get-step-results [self build-number] + (get @state-atom build-number))) (defn new-default-pipeline-state [config & {:keys [initial-state-for-testing]}] (let [home-dir (:home-dir config) diff --git a/test/clj/lambdacd/internal/default_pipeline_state_test.clj b/test/clj/lambdacd/internal/default_pipeline_state_test.clj index cb2d614f..db484d02 100644 --- a/test/clj/lambdacd/internal/default_pipeline_state_test.clj +++ b/test/clj/lambdacd/internal/default_pipeline_state_test.clj @@ -15,60 +15,66 @@ (def no-home-dir nil) (def keep-all-builds Integer/MAX_VALUE) -(defn- after-update [build id newstate] - (let [state (->DefaultPipelineState (atom clean-pipeline-state) (atom {}) no-home-dir keep-all-builds)] - (pipeline-state-record/update state build id newstate) - (pipeline-state-record/get-all state))) - (deftest general-pipeline-state-test (testing "that the next buildnumber is the highest build-number currently in the pipeline-state" (is (= 5 (next-build-number (->DefaultPipelineState (atom { 3 {} 4 {} 1 {}}) (atom {}) no-home-dir keep-all-builds)))) - (is (= 1 (next-build-number (->DefaultPipelineState (atom clean-pipeline-state) (atom {}) no-home-dir keep-all-builds))))) - (testing "that a new pipeline-state will be set on update" - (is (= { 10 { [0] { :foo :bar }}} (tu/without-ts (after-update 10 [0] {:foo :bar}))))) - (testing "that update will set a first-updated-at and most-recent-update-at timestamp" + (is (= 1 (next-build-number (->DefaultPipelineState (atom clean-pipeline-state) (atom {}) no-home-dir keep-all-builds)))))) + +(deftest pipeline-structure-state-test + (testing "that we can consume and retrieve pipeline-structure" + (let [component (new-default-pipeline-state {:home-dir (utils/create-temp-dir)})] + (protocols/consume-pipeline-structure component 1 {:some-pipeline :structure}) + (is (= {:some-pipeline :structure} (protocols/get-pipeline-structure component 1))))) + (testing "that we return nil if pipeline-structure isn't there" + (let [component (new-default-pipeline-state {:home-dir (utils/create-temp-dir)})] + (is (= nil (protocols/get-pipeline-structure component 43))))) + (testing "that we can retrieve the pipeline-structure from disk" + (let [home (utils/create-temp-dir) + component (new-default-pipeline-state {:home-dir home})] + (protocols/consume-pipeline-structure component 1 {:some-pipeline :structure}) + (let [other-component (new-default-pipeline-state {:home-dir home})] + (is (= {:some-pipeline :structure} (protocols/get-pipeline-structure other-component 1))))))) + +(deftest step-result-state-test + (testing "that we can consume and retrieve step results" + (let [component (new-default-pipeline-state {:home-dir (utils/create-temp-dir)})] + (protocols/consume-step-result-update component 1 [1 1] {:some :step-result}) + (is (= {[1 1] {:some :step-result}} (tu/without-ts (protocols/get-step-results component 1))))))(testing "that update will set a first-updated-at and most-recent-update-at timestamp" (let [first-update-timestamp (t/minus (t/now) (t/minutes 1)) last-updated-timestamp (t/now) - state (->DefaultPipelineState (atom clean-pipeline-state) (atom {}) no-home-dir keep-all-builds)] - (t/do-at first-update-timestamp (pipeline-state-record/update state 10 [0] {:foo :bar})) - (t/do-at last-updated-timestamp (pipeline-state-record/update state 10 [0] {:foo :baz})) - (is (= {10 {[0] {:foo :baz :most-recent-update-at last-updated-timestamp :first-updated-at first-update-timestamp}}} - (pipeline-state-record/get-all state))))) - (testing "that updating will save the current state to the file-system" - (let [home-dir (utils/create-temp-dir) - step-result {:foo :bar} - state (->DefaultPipelineState (atom clean-pipeline-state) (atom {}) home-dir keep-all-builds)] - (t/do-at (t/epoch) (pipeline-state-record/update state 10 [0] step-result)) - (is (= [{:step-id "0" - :step-result {:foo :bar - :most-recent-update-at (Date. 0) - :first-updated-at (Date. 0)}}] - (edn/read-string (slurp (str home-dir "/build-10/build-state.edn"))))))) - (testing "truncate" + component (new-default-pipeline-state {:home-dir (utils/create-temp-dir)})] + (t/do-at first-update-timestamp (protocols/consume-step-result-update component 1 [1 1] {:some :step-result})) + (t/do-at last-updated-timestamp (protocols/consume-step-result-update component 1 [1 1] {:some :step-result})) + (is (= {[1 1] {:some :step-result :most-recent-update-at last-updated-timestamp :first-updated-at first-update-timestamp}} (protocols/get-step-results component 1))))) + (testing "truncating" (testing "that updating will remove the oldest build if more than the maximum number of builds are written" (let [max-builds 5 - state (->DefaultPipelineState (atom clean-pipeline-state) (atom {}) no-home-dir max-builds)] - (doall (for [build (range 5)] - (pipeline-state-record/update state build [0] {:foo :baz :build build}))) - (is (= {[0] {:foo :baz :build 0}} (tu/without-ts (get (pipeline-state-record/get-all state) 0)))) - (is (= {[0] {:foo :baz :build 4}} (tu/without-ts (get (pipeline-state-record/get-all state) 4)))) + component (new-default-pipeline-state {:home-dir (utils/create-temp-dir) + :max-builds max-builds})] + (doall (for [build (range max-builds)] + (protocols/consume-step-result-update component build [1 1] {:foo :bar :build build}))) + + (is (= {[1 1] {:foo :bar :build 0}} (tu/without-ts (protocols/get-step-results component 0)))) + (is (= {[1 1] {:foo :bar :build 4}} (tu/without-ts (protocols/get-step-results component 4)))) - (pipeline-state-record/update state 5 [0] {:foo :baz :build 5}) + (protocols/consume-step-result-update component 5 [1 1] {:foo :bar :build 5}) - (is (nil? (tu/without-ts (get (pipeline-state-record/get-all state) 0)))) - (is (= {[0] {:foo :baz :build 4}} (tu/without-ts (get (pipeline-state-record/get-all state) 4)))) - (is (= {[0] {:foo :baz :build 5}} (tu/without-ts (get (pipeline-state-record/get-all state) 5)))))) + (is (= nil (protocols/get-step-results component 0))) + (is (= {[1 1] {:foo :bar :build 4}} (tu/without-ts (protocols/get-step-results component 4)))) + (is (= {[1 1] {:foo :bar :build 5}} (tu/without-ts (protocols/get-step-results component 5)))))) (testing "that updating will remove outdated state from file-system" (let [max-builds 5 - home-dir (utils/create-temp-dir) - state (->DefaultPipelineState (atom clean-pipeline-state) (atom {}) home-dir max-builds)] - (doall (for [build (range 5)] - (pipeline-state-record/update state build [0] {:foo :baz :build build}))) + home-dir (utils/create-temp-dir) + component (new-default-pipeline-state {:home-dir home-dir + :max-builds max-builds})] + (doall (for [build (range max-builds)] + (protocols/consume-step-result-update component build [1 1] {:foo :bar :build build}))) + (is (.exists (io/file home-dir "build-0/build-state.edn"))) (is (.exists (io/file home-dir "build-4/build-state.edn"))) (is (not (.exists (io/file home-dir "build-5/build-state.edn")))) - (pipeline-state-record/update state 5 [0] {:foo :baz :build 5}) + (protocols/consume-step-result-update component 5 [1 1] {:foo :bar :build 5}) (is (not (.exists (io/file home-dir "build-0/build-state.edn")))) (is (.exists (io/file home-dir "build-4/build-state.edn"))) @@ -76,30 +82,22 @@ (testing "that it will not accidently remove pipeline-structures for builds that arent truncated but don't have a state yet" (let [max-builds 10 home-dir (utils/create-temp-dir) - state (->DefaultPipelineState (atom clean-pipeline-state) (atom {}) home-dir max-builds) + state (new-default-pipeline-state {:home-dir home-dir + :max-builds max-builds}) build-dir (io/file home-dir "build-6") pipeline-structure-file (io/file build-dir "pipeine-structure.edn")] - (doall (for [build (range 5)] - (pipeline-state-record/update state build [0] {:foo :baz :build build}))) + (doall (for [build (range max-builds)] + (protocols/consume-step-result-update state build [0] {:foo :baz :build build}))) (.mkdirs build-dir) (spit pipeline-structure-file "test") (is (.exists pipeline-structure-file)) - (pipeline-state-record/update state 5 [0] {:foo :baz :build 5}) + (protocols/consume-step-result-update state 5 [0] {:foo :baz :build 5}) - (is (.exists pipeline-structure-file)))))) - -(deftest pipeline-structure-state-test - (testing "that we can consume and retrieve pipeline-structure" - (let [component (new-default-pipeline-state {:home-dir (utils/create-temp-dir)})] - (protocols/consume-pipeline-structure component 1 {:some-pipeline :structure}) - (is (= {:some-pipeline :structure} (protocols/get-pipeline-structure component 1))))) - (testing "that we return nil if pipeline-structure isn't there" - (let [component (new-default-pipeline-state {:home-dir (utils/create-temp-dir)})] - (is (= nil (protocols/get-pipeline-structure component 43))))) + (is (.exists pipeline-structure-file))))) (testing "that we can retrieve the pipeline-structure from disk" - (let [home (utils/create-temp-dir) - component (new-default-pipeline-state {:home-dir home})] - (protocols/consume-pipeline-structure component 1 {:some-pipeline :structure}) - (let [other-component (new-default-pipeline-state {:home-dir home})] - (is (= {:some-pipeline :structure} (protocols/get-pipeline-structure other-component 1))))))) + (let [home-dir (utils/create-temp-dir) + component (new-default-pipeline-state {:home-dir home-dir})] + (protocols/consume-step-result-update component 1 [1 1] {:some :step-result}) + (let [other-component (new-default-pipeline-state {:home-dir home-dir})] + (is (= {[1 1] {:some :step-result}} (tu/without-ts (protocols/get-step-results other-component 1))))))))