| OLD | NEW |
| 1 // Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include <gtest/gtest.h> | 5 #include <gtest/gtest.h> |
| 6 | 6 |
| 7 #include "update_engine/update_attempter_mock.h" | 7 #include "update_engine/update_attempter_mock.h" |
| 8 #include "update_engine/update_check_scheduler.h" | 8 #include "update_engine/update_check_scheduler.h" |
| 9 | 9 |
| 10 using std::string; | 10 using std::string; |
| (...skipping 19 matching lines...) Expand all Loading... |
| 30 public: | 30 public: |
| 31 UpdateCheckSchedulerUnderTest(UpdateAttempter* update_attempter) | 31 UpdateCheckSchedulerUnderTest(UpdateAttempter* update_attempter) |
| 32 : UpdateCheckScheduler(update_attempter) {} | 32 : UpdateCheckScheduler(update_attempter) {} |
| 33 | 33 |
| 34 MOCK_METHOD2(GTimeoutAddSeconds, guint(guint seconds, GSourceFunc function)); | 34 MOCK_METHOD2(GTimeoutAddSeconds, guint(guint seconds, GSourceFunc function)); |
| 35 MOCK_METHOD0(IsBootDeviceRemovable, bool()); | 35 MOCK_METHOD0(IsBootDeviceRemovable, bool()); |
| 36 MOCK_METHOD0(IsOfficialBuild, bool()); | 36 MOCK_METHOD0(IsOfficialBuild, bool()); |
| 37 }; | 37 }; |
| 38 | 38 |
| 39 class UpdateCheckSchedulerTest : public ::testing::Test { | 39 class UpdateCheckSchedulerTest : public ::testing::Test { |
| 40 public: |
| 41 UpdateCheckSchedulerTest() : scheduler_(&attempter_) {} |
| 42 |
| 40 protected: | 43 protected: |
| 41 virtual void SetUp() { | 44 virtual void SetUp() { |
| 42 test_ = this; | 45 test_ = this; |
| 43 loop_ = NULL; | 46 loop_ = NULL; |
| 44 scheduler_.reset(new UpdateCheckSchedulerUnderTest(&attempter_)); | 47 EXPECT_EQ(&attempter_, scheduler_.update_attempter_); |
| 45 EXPECT_EQ(&attempter_, scheduler_->update_attempter_); | 48 EXPECT_FALSE(scheduler_.enabled_); |
| 46 EXPECT_FALSE(scheduler_->enabled_); | 49 EXPECT_FALSE(scheduler_.scheduled_); |
| 47 EXPECT_FALSE(scheduler_->scheduled_); | 50 EXPECT_EQ(0, scheduler_.last_interval_); |
| 48 EXPECT_EQ(0, scheduler_->last_interval_); | |
| 49 } | 51 } |
| 50 | 52 |
| 51 virtual void TearDown() { | 53 virtual void TearDown() { |
| 52 test_ = NULL; | 54 test_ = NULL; |
| 53 loop_ = NULL; | 55 loop_ = NULL; |
| 54 scheduler_.reset(NULL); | |
| 55 } | 56 } |
| 56 | 57 |
| 57 static gboolean SourceCallback(gpointer data) { | 58 static gboolean SourceCallback(gpointer data) { |
| 58 g_main_loop_quit(test_->loop_); | 59 g_main_loop_quit(test_->loop_); |
| 59 // Forwards the call to the function mock so that expectations can be set. | 60 // Forwards the call to the function mock so that expectations can be set. |
| 60 return test_->source_callback_.Call(data); | 61 return test_->source_callback_.Call(data); |
| 61 } | 62 } |
| 62 | 63 |
| 63 scoped_ptr<UpdateCheckSchedulerUnderTest> scheduler_; | 64 UpdateCheckSchedulerUnderTest scheduler_; |
| 64 UpdateAttempterMock attempter_; | 65 UpdateAttempterMock attempter_; |
| 65 MockFunction<gboolean(gpointer data)> source_callback_; | 66 MockFunction<gboolean(gpointer data)> source_callback_; |
| 66 GMainLoop* loop_; | 67 GMainLoop* loop_; |
| 67 static UpdateCheckSchedulerTest* test_; | 68 static UpdateCheckSchedulerTest* test_; |
| 68 }; | 69 }; |
| 69 | 70 |
| 70 UpdateCheckSchedulerTest* UpdateCheckSchedulerTest::test_ = NULL; | 71 UpdateCheckSchedulerTest* UpdateCheckSchedulerTest::test_ = NULL; |
| 71 | 72 |
| 72 TEST_F(UpdateCheckSchedulerTest, CanScheduleTest) { | 73 TEST_F(UpdateCheckSchedulerTest, CanScheduleTest) { |
| 73 EXPECT_FALSE(scheduler_->CanSchedule()); | 74 EXPECT_FALSE(scheduler_.CanSchedule()); |
| 74 scheduler_->enabled_ = true; | 75 scheduler_.enabled_ = true; |
| 75 EXPECT_TRUE(scheduler_->CanSchedule()); | 76 EXPECT_TRUE(scheduler_.CanSchedule()); |
| 76 scheduler_->scheduled_ = true; | 77 scheduler_.scheduled_ = true; |
| 77 EXPECT_FALSE(scheduler_->CanSchedule()); | 78 EXPECT_FALSE(scheduler_.CanSchedule()); |
| 78 scheduler_->enabled_ = false; | 79 scheduler_.enabled_ = false; |
| 79 EXPECT_FALSE(scheduler_->CanSchedule()); | 80 EXPECT_FALSE(scheduler_.CanSchedule()); |
| 80 } | 81 } |
| 81 | 82 |
| 82 TEST_F(UpdateCheckSchedulerTest, ComputeNextIntervalAndFuzzBackoffTest) { | 83 TEST_F(UpdateCheckSchedulerTest, ComputeNextIntervalAndFuzzBackoffTest) { |
| 83 int interval, fuzz; | 84 int interval, fuzz; |
| 84 attempter_.set_http_response_code(500); | 85 attempter_.set_http_response_code(500); |
| 85 int last_interval = UpdateCheckScheduler::kTimeoutPeriodic + 50; | 86 int last_interval = UpdateCheckScheduler::kTimeoutPeriodic + 50; |
| 86 scheduler_->last_interval_ = last_interval; | 87 scheduler_.last_interval_ = last_interval; |
| 87 scheduler_->ComputeNextIntervalAndFuzz(&interval, &fuzz); | 88 scheduler_.ComputeNextIntervalAndFuzz(&interval, &fuzz); |
| 88 EXPECT_EQ(2 * last_interval, interval); | 89 EXPECT_EQ(2 * last_interval, interval); |
| 89 EXPECT_EQ(2 * last_interval, fuzz); | 90 EXPECT_EQ(2 * last_interval, fuzz); |
| 90 | 91 |
| 91 attempter_.set_http_response_code(503); | 92 attempter_.set_http_response_code(503); |
| 92 last_interval = UpdateCheckScheduler::kTimeoutMaxBackoff / 2 + 1; | 93 last_interval = UpdateCheckScheduler::kTimeoutMaxBackoff / 2 + 1; |
| 93 scheduler_->last_interval_ = last_interval; | 94 scheduler_.last_interval_ = last_interval; |
| 94 scheduler_->ComputeNextIntervalAndFuzz(&interval, &fuzz); | 95 scheduler_.ComputeNextIntervalAndFuzz(&interval, &fuzz); |
| 95 EXPECT_EQ(UpdateCheckScheduler::kTimeoutMaxBackoff, interval); | 96 EXPECT_EQ(UpdateCheckScheduler::kTimeoutMaxBackoff, interval); |
| 96 EXPECT_EQ(UpdateCheckScheduler::kTimeoutMaxBackoff, fuzz); | 97 EXPECT_EQ(UpdateCheckScheduler::kTimeoutMaxBackoff, fuzz); |
| 97 } | 98 } |
| 98 | 99 |
| 99 TEST_F(UpdateCheckSchedulerTest, ComputeNextIntervalAndFuzzTest) { | 100 TEST_F(UpdateCheckSchedulerTest, ComputeNextIntervalAndFuzzTest) { |
| 100 int interval, fuzz; | 101 int interval, fuzz; |
| 101 scheduler_->ComputeNextIntervalAndFuzz(&interval, &fuzz); | 102 scheduler_.ComputeNextIntervalAndFuzz(&interval, &fuzz); |
| 102 EXPECT_EQ(UpdateCheckScheduler::kTimeoutPeriodic, interval); | 103 EXPECT_EQ(UpdateCheckScheduler::kTimeoutPeriodic, interval); |
| 103 EXPECT_EQ(UpdateCheckScheduler::kTimeoutRegularFuzz, fuzz); | 104 EXPECT_EQ(UpdateCheckScheduler::kTimeoutRegularFuzz, fuzz); |
| 104 } | 105 } |
| 105 | 106 |
| 106 TEST_F(UpdateCheckSchedulerTest, GTimeoutAddSecondsTest) { | 107 TEST_F(UpdateCheckSchedulerTest, GTimeoutAddSecondsTest) { |
| 107 loop_ = g_main_loop_new(g_main_context_default(), FALSE); | 108 loop_ = g_main_loop_new(g_main_context_default(), FALSE); |
| 108 // Invokes the actual GLib wrapper method rather than the subclass mock. | 109 // Invokes the actual GLib wrapper method rather than the subclass mock. |
| 109 scheduler_->UpdateCheckScheduler::GTimeoutAddSeconds(0, SourceCallback); | 110 scheduler_.UpdateCheckScheduler::GTimeoutAddSeconds(0, SourceCallback); |
| 110 EXPECT_CALL(source_callback_, Call(scheduler_.get())).Times(1); | 111 EXPECT_CALL(source_callback_, Call(&scheduler_)).Times(1); |
| 111 g_main_loop_run(loop_); | 112 g_main_loop_run(loop_); |
| 112 g_main_loop_unref(loop_); | 113 g_main_loop_unref(loop_); |
| 113 } | 114 } |
| 114 | 115 |
| 115 TEST_F(UpdateCheckSchedulerTest, IsBootDeviceRemovableTest) { | 116 TEST_F(UpdateCheckSchedulerTest, IsBootDeviceRemovableTest) { |
| 116 // Invokes the actual utils wrapper method rather than the subclass mock. | 117 // Invokes the actual utils wrapper method rather than the subclass mock. |
| 117 EXPECT_FALSE(scheduler_->UpdateCheckScheduler::IsBootDeviceRemovable()); | 118 EXPECT_FALSE(scheduler_.UpdateCheckScheduler::IsBootDeviceRemovable()); |
| 118 } | 119 } |
| 119 | 120 |
| 120 TEST_F(UpdateCheckSchedulerTest, IsOfficialBuildTest) { | 121 TEST_F(UpdateCheckSchedulerTest, IsOfficialBuildTest) { |
| 121 // Invokes the actual utils wrapper method rather than the subclass mock. | 122 // Invokes the actual utils wrapper method rather than the subclass mock. |
| 122 EXPECT_TRUE(scheduler_->UpdateCheckScheduler::IsOfficialBuild()); | 123 EXPECT_TRUE(scheduler_.UpdateCheckScheduler::IsOfficialBuild()); |
| 123 } | 124 } |
| 124 | 125 |
| 125 TEST_F(UpdateCheckSchedulerTest, RunBootDeviceRemovableTest) { | 126 TEST_F(UpdateCheckSchedulerTest, RunBootDeviceRemovableTest) { |
| 126 scheduler_->enabled_ = true; | 127 scheduler_.enabled_ = true; |
| 127 EXPECT_CALL(*scheduler_, IsOfficialBuild()).Times(1).WillOnce(Return(true)); | 128 EXPECT_CALL(scheduler_, IsOfficialBuild()).Times(1).WillOnce(Return(true)); |
| 128 EXPECT_CALL(*scheduler_, IsBootDeviceRemovable()) | 129 EXPECT_CALL(scheduler_, IsBootDeviceRemovable()) |
| 129 .Times(1) | 130 .Times(1) |
| 130 .WillOnce(Return(true)); | 131 .WillOnce(Return(true)); |
| 131 scheduler_->Run(); | 132 scheduler_.Run(); |
| 132 EXPECT_FALSE(scheduler_->enabled_); | 133 EXPECT_FALSE(scheduler_.enabled_); |
| 133 EXPECT_EQ(NULL, attempter_.update_check_scheduler()); | 134 EXPECT_EQ(NULL, attempter_.update_check_scheduler()); |
| 134 } | 135 } |
| 135 | 136 |
| 136 TEST_F(UpdateCheckSchedulerTest, RunNonOfficialBuildTest) { | 137 TEST_F(UpdateCheckSchedulerTest, RunNonOfficialBuildTest) { |
| 137 scheduler_->enabled_ = true; | 138 scheduler_.enabled_ = true; |
| 138 EXPECT_CALL(*scheduler_, IsOfficialBuild()).Times(1).WillOnce(Return(false)); | 139 EXPECT_CALL(scheduler_, IsOfficialBuild()).Times(1).WillOnce(Return(false)); |
| 139 scheduler_->Run(); | 140 scheduler_.Run(); |
| 140 EXPECT_FALSE(scheduler_->enabled_); | 141 EXPECT_FALSE(scheduler_.enabled_); |
| 141 EXPECT_EQ(NULL, attempter_.update_check_scheduler()); | 142 EXPECT_EQ(NULL, attempter_.update_check_scheduler()); |
| 142 } | 143 } |
| 143 | 144 |
| 144 TEST_F(UpdateCheckSchedulerTest, RunTest) { | 145 TEST_F(UpdateCheckSchedulerTest, RunTest) { |
| 145 int interval_min, interval_max; | 146 int interval_min, interval_max; |
| 146 FuzzRange(UpdateCheckScheduler::kTimeoutOnce, | 147 FuzzRange(UpdateCheckScheduler::kTimeoutOnce, |
| 147 UpdateCheckScheduler::kTimeoutRegularFuzz, | 148 UpdateCheckScheduler::kTimeoutRegularFuzz, |
| 148 &interval_min, | 149 &interval_min, |
| 149 &interval_max); | 150 &interval_max); |
| 150 EXPECT_CALL(*scheduler_, IsOfficialBuild()).Times(1).WillOnce(Return(true)); | 151 EXPECT_CALL(scheduler_, IsOfficialBuild()).Times(1).WillOnce(Return(true)); |
| 151 EXPECT_CALL(*scheduler_, IsBootDeviceRemovable()) | 152 EXPECT_CALL(scheduler_, IsBootDeviceRemovable()) |
| 152 .Times(1) | 153 .Times(1) |
| 153 .WillOnce(Return(false)); | 154 .WillOnce(Return(false)); |
| 154 EXPECT_CALL(*scheduler_, | 155 EXPECT_CALL(scheduler_, |
| 155 GTimeoutAddSeconds(AllOf(Ge(interval_min), Le(interval_max)), | 156 GTimeoutAddSeconds(AllOf(Ge(interval_min), Le(interval_max)), |
| 156 scheduler_->StaticCheck)).Times(1); | 157 scheduler_.StaticCheck)).Times(1); |
| 157 scheduler_->Run(); | 158 scheduler_.Run(); |
| 158 EXPECT_TRUE(scheduler_->enabled_); | 159 EXPECT_TRUE(scheduler_.enabled_); |
| 159 EXPECT_EQ(scheduler_.get(), attempter_.update_check_scheduler()); | 160 EXPECT_EQ(&scheduler_, attempter_.update_check_scheduler()); |
| 160 } | 161 } |
| 161 | 162 |
| 162 TEST_F(UpdateCheckSchedulerTest, ScheduleCheckDisabledTest) { | 163 TEST_F(UpdateCheckSchedulerTest, ScheduleCheckDisabledTest) { |
| 163 EXPECT_CALL(*scheduler_, GTimeoutAddSeconds(_, _)).Times(0); | 164 EXPECT_CALL(scheduler_, GTimeoutAddSeconds(_, _)).Times(0); |
| 164 scheduler_->ScheduleCheck(250, 30); | 165 scheduler_.ScheduleCheck(250, 30); |
| 165 EXPECT_EQ(0, scheduler_->last_interval_); | 166 EXPECT_EQ(0, scheduler_.last_interval_); |
| 166 EXPECT_FALSE(scheduler_->scheduled_); | 167 EXPECT_FALSE(scheduler_.scheduled_); |
| 167 } | 168 } |
| 168 | 169 |
| 169 TEST_F(UpdateCheckSchedulerTest, ScheduleCheckEnabledTest) { | 170 TEST_F(UpdateCheckSchedulerTest, ScheduleCheckEnabledTest) { |
| 170 int interval_min, interval_max; | 171 int interval_min, interval_max; |
| 171 FuzzRange(100, 10, &interval_min,&interval_max); | 172 FuzzRange(100, 10, &interval_min,&interval_max); |
| 172 EXPECT_CALL(*scheduler_, | 173 EXPECT_CALL(scheduler_, |
| 173 GTimeoutAddSeconds(AllOf(Ge(interval_min), Le(interval_max)), | 174 GTimeoutAddSeconds(AllOf(Ge(interval_min), Le(interval_max)), |
| 174 scheduler_->StaticCheck)).Times(1); | 175 scheduler_.StaticCheck)).Times(1); |
| 175 scheduler_->enabled_ = true; | 176 scheduler_.enabled_ = true; |
| 176 scheduler_->ScheduleCheck(100, 10); | 177 scheduler_.ScheduleCheck(100, 10); |
| 177 EXPECT_EQ(100, scheduler_->last_interval_); | 178 EXPECT_EQ(100, scheduler_.last_interval_); |
| 178 EXPECT_TRUE(scheduler_->scheduled_); | 179 EXPECT_TRUE(scheduler_.scheduled_); |
| 179 } | 180 } |
| 180 | 181 |
| 181 TEST_F(UpdateCheckSchedulerTest, ScheduleCheckNegativeIntervalTest) { | 182 TEST_F(UpdateCheckSchedulerTest, ScheduleCheckNegativeIntervalTest) { |
| 182 EXPECT_CALL(*scheduler_, GTimeoutAddSeconds(0, scheduler_->StaticCheck)) | 183 EXPECT_CALL(scheduler_, GTimeoutAddSeconds(0, scheduler_.StaticCheck)) |
| 183 .Times(1); | 184 .Times(1); |
| 184 scheduler_->enabled_ = true; | 185 scheduler_.enabled_ = true; |
| 185 scheduler_->ScheduleCheck(-50, 20); | 186 scheduler_.ScheduleCheck(-50, 20); |
| 186 EXPECT_TRUE(scheduler_->scheduled_); | 187 EXPECT_TRUE(scheduler_.scheduled_); |
| 187 } | 188 } |
| 188 | 189 |
| 189 TEST_F(UpdateCheckSchedulerTest, ScheduleNextCheckDisabledTest) { | 190 TEST_F(UpdateCheckSchedulerTest, ScheduleNextCheckDisabledTest) { |
| 190 EXPECT_CALL(*scheduler_, GTimeoutAddSeconds(_, _)).Times(0); | 191 EXPECT_CALL(scheduler_, GTimeoutAddSeconds(_, _)).Times(0); |
| 191 scheduler_->ScheduleNextCheck(); | 192 scheduler_.ScheduleNextCheck(); |
| 192 } | 193 } |
| 193 | 194 |
| 194 TEST_F(UpdateCheckSchedulerTest, ScheduleNextCheckEnabledTest) { | 195 TEST_F(UpdateCheckSchedulerTest, ScheduleNextCheckEnabledTest) { |
| 195 int interval_min, interval_max; | 196 int interval_min, interval_max; |
| 196 FuzzRange(UpdateCheckScheduler::kTimeoutPeriodic, | 197 FuzzRange(UpdateCheckScheduler::kTimeoutPeriodic, |
| 197 UpdateCheckScheduler::kTimeoutRegularFuzz, | 198 UpdateCheckScheduler::kTimeoutRegularFuzz, |
| 198 &interval_min, | 199 &interval_min, |
| 199 &interval_max); | 200 &interval_max); |
| 200 EXPECT_CALL(*scheduler_, | 201 EXPECT_CALL(scheduler_, |
| 201 GTimeoutAddSeconds(AllOf(Ge(interval_min), Le(interval_max)), | 202 GTimeoutAddSeconds(AllOf(Ge(interval_min), Le(interval_max)), |
| 202 scheduler_->StaticCheck)).Times(1); | 203 scheduler_.StaticCheck)).Times(1); |
| 203 scheduler_->enabled_ = true; | 204 scheduler_.enabled_ = true; |
| 204 scheduler_->ScheduleNextCheck(); | 205 scheduler_.ScheduleNextCheck(); |
| 205 } | 206 } |
| 206 | 207 |
| 207 TEST_F(UpdateCheckSchedulerTest, SetUpdateStatusIdleDisabledTest) { | 208 TEST_F(UpdateCheckSchedulerTest, SetUpdateStatusIdleDisabledTest) { |
| 208 EXPECT_CALL(*scheduler_, GTimeoutAddSeconds(_, _)).Times(0); | 209 EXPECT_CALL(scheduler_, GTimeoutAddSeconds(_, _)).Times(0); |
| 209 scheduler_->SetUpdateStatus(UPDATE_STATUS_IDLE); | 210 scheduler_.SetUpdateStatus(UPDATE_STATUS_IDLE); |
| 210 } | 211 } |
| 211 | 212 |
| 212 TEST_F(UpdateCheckSchedulerTest, SetUpdateStatusIdleEnabledTest) { | 213 TEST_F(UpdateCheckSchedulerTest, SetUpdateStatusIdleEnabledTest) { |
| 213 int interval_min, interval_max; | 214 int interval_min, interval_max; |
| 214 FuzzRange(UpdateCheckScheduler::kTimeoutPeriodic, | 215 FuzzRange(UpdateCheckScheduler::kTimeoutPeriodic, |
| 215 UpdateCheckScheduler::kTimeoutRegularFuzz, | 216 UpdateCheckScheduler::kTimeoutRegularFuzz, |
| 216 &interval_min, | 217 &interval_min, |
| 217 &interval_max); | 218 &interval_max); |
| 218 EXPECT_CALL(*scheduler_, | 219 EXPECT_CALL(scheduler_, |
| 219 GTimeoutAddSeconds(AllOf(Ge(interval_min), Le(interval_max)), | 220 GTimeoutAddSeconds(AllOf(Ge(interval_min), Le(interval_max)), |
| 220 scheduler_->StaticCheck)).Times(1); | 221 scheduler_.StaticCheck)).Times(1); |
| 221 scheduler_->enabled_ = true; | 222 scheduler_.enabled_ = true; |
| 222 scheduler_->SetUpdateStatus(UPDATE_STATUS_IDLE); | 223 scheduler_.SetUpdateStatus(UPDATE_STATUS_IDLE); |
| 223 } | 224 } |
| 224 | 225 |
| 225 TEST_F(UpdateCheckSchedulerTest, SetUpdateStatusNonIdleTest) { | 226 TEST_F(UpdateCheckSchedulerTest, SetUpdateStatusNonIdleTest) { |
| 226 EXPECT_CALL(*scheduler_, GTimeoutAddSeconds(_, _)).Times(0); | 227 EXPECT_CALL(scheduler_, GTimeoutAddSeconds(_, _)).Times(0); |
| 227 scheduler_->SetUpdateStatus(UPDATE_STATUS_DOWNLOADING); | 228 scheduler_.SetUpdateStatus(UPDATE_STATUS_DOWNLOADING); |
| 228 scheduler_->enabled_ = true; | 229 scheduler_.enabled_ = true; |
| 229 scheduler_->SetUpdateStatus(UPDATE_STATUS_DOWNLOADING); | 230 scheduler_.SetUpdateStatus(UPDATE_STATUS_DOWNLOADING); |
| 230 } | 231 } |
| 231 | 232 |
| 232 TEST_F(UpdateCheckSchedulerTest, StaticCheckTest) { | 233 TEST_F(UpdateCheckSchedulerTest, StaticCheckTest) { |
| 233 scheduler_->scheduled_ = true; | 234 scheduler_.scheduled_ = true; |
| 234 EXPECT_CALL(attempter_, Update("", "")).Times(1); | 235 EXPECT_CALL(attempter_, Update("", "")).Times(1); |
| 235 UpdateCheckSchedulerUnderTest::StaticCheck(scheduler_.get()); | 236 UpdateCheckSchedulerUnderTest::StaticCheck(&scheduler_); |
| 236 } | 237 } |
| 237 | 238 |
| 238 } // namespace chromeos_update_engine | 239 } // namespace chromeos_update_engine |
| OLD | NEW |