tests.rs 42.9 KiB
Newer Older
					},
				),
			]
		);
	});
}

// We understand that this does not work as intended for leases that expire within `region_length`
// timeslices after calling `start_sales`.
#[test]
fn short_leases_cannot_be_renewed() {
	TestExt::new().endow(1, 1000).execute_with(|| {
		// Timeslice period is 2.
		//
		// Sale 1 starts at block 7, Sale 2 starts at 13.

		// Set lease to expire in sale period 0 and start sales.
		assert_ok!(Broker::do_set_lease(2001, 3));
		assert_eq!(Leases::<Test>::get().len(), 1);
		// Start the sales with one core for this lease.

		// The lease is removed.
		assert_eq!(Leases::<Test>::get().len(), 0);

		// We should have got an entry in AllowedRenewals, but we don't because rotate_sale
		// schedules leases a period in advance. This renewal should be in the period after next
		// because while bootstrapping our way into the sale periods, we give everything a lease for
		// period 1, so they can renew for period 2. So we have a core until the end of period 1,
		// but we are not marked as able to renew because we expired before sale period 1 starts.
		//
		// This should be fixed.
		assert_eq!(AllowedRenewals::<Test>::get(AllowedRenewalId { core: 0, when: 10 }), None);
		// And the lease has been removed from storage.
		assert_eq!(Leases::<Test>::get().len(), 0);

		// Advance to sale period 2, where we now cannot renew.
		advance_to(13);
		assert_noop!(Broker::do_renew(1, 0), Error::<Test>::NotAllowed);

		// Check the trace.
		assert_eq!(
			CoretimeTrace::get(),
			vec![
				// Period 0 gets no assign core, but leases are on-core.
				// Period 1 we get assigned a core due to the way the sales are bootstrapped.
				(
					6,
					AssignCore {
						core: 0,
						begin: 8,
						assignment: vec![(CoreAssignment::Task(2001), 57600)],
						end_hint: None,
					},
				),
				// Period 2 - we don't get a core as we couldn't renew.
				// This core is recycled into the pool.
				(
					12,
					AssignCore {
						core: 0,
						begin: 14,
						assignment: vec![(CoreAssignment::Pool, 57600)],
						end_hint: None,
					},
				),
			]
		);
	});
}

#[test]
fn leases_are_limited() {
	TestExt::new().execute_with(|| {
		let max_leases: u32 = <Test as Config>::MaxLeasedCores::get();
		Leases::<Test>::put(
			BoundedVec::try_from(vec![
				LeaseRecordItem { task: 1u32, until: 10u32 };
				max_leases as usize
			])
			.unwrap(),
		);
		assert_noop!(Broker::do_set_lease(1000, 10), Error::<Test>::TooManyLeases);
	});
}

#[test]
fn purchase_requires_valid_status_and_sale_info() {
	TestExt::new().execute_with(|| {
		assert_noop!(Broker::do_purchase(1, 100), Error::<Test>::Uninitialized);

		let status = StatusRecord {
			core_count: 2,
			private_pool_size: 0,
			system_pool_size: 0,
			last_committed_timeslice: 0,
			last_timeslice: 1,
		};
		Status::<Test>::put(&status);
		assert_noop!(Broker::do_purchase(1, 100), Error::<Test>::NoSales);

		let mut dummy_sale = SaleInfoRecord {
			sale_start: 0,
			leadin_length: 0,
			price: 200,
			sellout_price: None,
			region_begin: 0,
			region_end: 3,
			first_core: 3,
			ideal_cores_sold: 0,
			cores_offered: 1,
			cores_sold: 2,
		};
		SaleInfo::<Test>::put(&dummy_sale);
		assert_noop!(Broker::do_purchase(1, 100), Error::<Test>::Unavailable);

		dummy_sale.first_core = 1;
		SaleInfo::<Test>::put(&dummy_sale);
		assert_noop!(Broker::do_purchase(1, 100), Error::<Test>::SoldOut);

		assert_ok!(Broker::do_start_sales(200, 1));
		assert_noop!(Broker::do_purchase(1, 100), Error::<Test>::TooEarly);

		advance_to(2);
		assert_noop!(Broker::do_purchase(1, 100), Error::<Test>::Overpriced);
	});
}

#[test]
fn renewal_requires_valid_status_and_sale_info() {
	TestExt::new().execute_with(|| {
		assert_noop!(Broker::do_renew(1, 1), Error::<Test>::Uninitialized);

		let status = StatusRecord {
			core_count: 2,
			private_pool_size: 0,
			system_pool_size: 0,
			last_committed_timeslice: 0,
			last_timeslice: 1,
		};
		Status::<Test>::put(&status);
		assert_noop!(Broker::do_renew(1, 1), Error::<Test>::NoSales);

		let mut dummy_sale = SaleInfoRecord {
			sale_start: 0,
			leadin_length: 0,
			price: 200,
			sellout_price: None,
			region_begin: 0,
			region_end: 3,
			first_core: 3,
			ideal_cores_sold: 0,
			cores_offered: 1,
			cores_sold: 2,
		};
		SaleInfo::<Test>::put(&dummy_sale);
		assert_noop!(Broker::do_renew(1, 1), Error::<Test>::Unavailable);

		dummy_sale.first_core = 1;
		SaleInfo::<Test>::put(&dummy_sale);
		assert_noop!(Broker::do_renew(1, 1), Error::<Test>::SoldOut);

		assert_ok!(Broker::do_start_sales(200, 1));
		assert_noop!(Broker::do_renew(1, 1), Error::<Test>::NotAllowed);

		let record = AllowedRenewalRecord {
			price: 100,
			completion: CompletionStatus::Partial(CoreMask::from_chunk(0, 20)),
		};
		AllowedRenewals::<Test>::insert(AllowedRenewalId { core: 1, when: 4 }, &record);
		assert_noop!(Broker::do_renew(1, 1), Error::<Test>::IncompleteAssignment);
	});
}

#[test]
fn cannot_transfer_or_partition_or_interlace_unknown() {
	TestExt::new().execute_with(|| {
		let region_id = RegionId { begin: 0, core: 0, mask: CoreMask::complete() };
		assert_noop!(Broker::do_transfer(region_id, None, 2), Error::<Test>::UnknownRegion);
		assert_noop!(Broker::do_partition(region_id, None, 2), Error::<Test>::UnknownRegion);
		assert_noop!(
			Broker::do_interlace(region_id, None, CoreMask::from_chunk(0, 20)),
			Error::<Test>::UnknownRegion
		);
	});
}

#[test]
fn check_ownership_for_transfer_or_partition_or_interlace() {
	TestExt::new().endow(1, 1000).execute_with(|| {
		assert_ok!(Broker::do_start_sales(100, 1));
		advance_to(2);
		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
		assert_noop!(Broker::do_transfer(region, Some(2), 2), Error::<Test>::NotOwner);
		assert_noop!(Broker::do_partition(region, Some(2), 2), Error::<Test>::NotOwner);
		assert_noop!(
			Broker::do_interlace(region, Some(2), CoreMask::from_chunk(0, 20)),
			Error::<Test>::NotOwner
		);
	});
}

#[test]
fn cannot_partition_invalid_offset() {
	TestExt::new().endow(1, 1000).execute_with(|| {
		assert_ok!(Broker::do_start_sales(100, 1));
		advance_to(2);
		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
		assert_noop!(Broker::do_partition(region, None, 0), Error::<Test>::PivotTooEarly);
		assert_noop!(Broker::do_partition(region, None, 5), Error::<Test>::PivotTooLate);
	});
}

#[test]
fn cannot_interlace_invalid_pivot() {
	TestExt::new().endow(1, 1000).execute_with(|| {
		assert_ok!(Broker::do_start_sales(100, 1));
		advance_to(2);
		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
		let (region1, _) = Broker::do_interlace(region, None, CoreMask::from_chunk(0, 20)).unwrap();
		assert_noop!(
			Broker::do_interlace(region1, None, CoreMask::from_chunk(20, 40)),
			Error::<Test>::ExteriorPivot
		);
		assert_noop!(
			Broker::do_interlace(region1, None, CoreMask::void()),
			Error::<Test>::VoidPivot
		);
		assert_noop!(
			Broker::do_interlace(region1, None, CoreMask::from_chunk(0, 20)),
			Error::<Test>::CompletePivot
		);
	});
}

#[test]
fn assign_should_drop_invalid_region() {
	TestExt::new().endow(1, 1000).execute_with(|| {
		assert_ok!(Broker::do_start_sales(100, 1));
		advance_to(2);
		let mut region = Broker::do_purchase(1, u64::max_value()).unwrap();
		advance_to(10);
		assert_ok!(Broker::do_assign(region, Some(1), 1001, Provisional));
		region.begin = 7;
		System::assert_last_event(Event::RegionDropped { region_id: region, duration: 3 }.into());
	});
}

#[test]
fn pool_should_drop_invalid_region() {
	TestExt::new().endow(1, 1000).execute_with(|| {
		assert_ok!(Broker::do_start_sales(100, 1));
		advance_to(2);
		let mut region = Broker::do_purchase(1, u64::max_value()).unwrap();
		advance_to(10);
		assert_ok!(Broker::do_pool(region, Some(1), 1001, Provisional));
		region.begin = 7;
		System::assert_last_event(Event::RegionDropped { region_id: region, duration: 3 }.into());
	});
}

#[test]
fn config_works() {
	TestExt::new().execute_with(|| {
		let mut cfg = new_config();
		// Good config works:
		assert_ok!(Broker::configure(Root.into(), cfg.clone()));
		// Bad config is a noop:
		cfg.leadin_length = 0;
		assert_noop!(Broker::configure(Root.into(), cfg), Error::<Test>::InvalidConfig);
	});
}

/// Ensure that a lease that ended before `start_sales` was called can be renewed.
#[test]
fn renewal_works_leases_ended_before_start_sales() {
	TestExt::new().endow(1, 1000).execute_with(|| {
		let config = Configuration::<Test>::get().unwrap();

		// This lease is ended before `start_stales` was called.
		assert_ok!(Broker::do_set_lease(1, 1));

		// Go to some block to ensure that the lease of task 1 already ended.
		advance_to(5);

		// This lease will end three sale periods in.
		assert_ok!(Broker::do_set_lease(
			2,
			Broker::latest_timeslice_ready_to_commit(&config) + config.region_length * 3
		));

		// This intializes the first sale and the period 0.
		assert_noop!(Broker::do_renew(1, 1), Error::<Test>::Unavailable);
		assert_noop!(Broker::do_renew(1, 0), Error::<Test>::Unavailable);

		// Lease for task 1 should have been dropped.
		assert!(Leases::<Test>::get().iter().any(|l| l.task == 2));

		// This intializes the second and the period 1.
		advance_sale_period();

		// Now we can finally renew the core 0 of task 1.
		let new_core = Broker::do_renew(1, 0).unwrap();
		// Renewing the active lease doesn't work.
		assert_noop!(Broker::do_renew(1, 1), Error::<Test>::SoldOut);
		assert_eq!(balance(1), 900);

		// This intializes the third sale and the period 2.
		advance_sale_period();
		let new_core = Broker::do_renew(1, new_core).unwrap();

		// Renewing the active lease doesn't work.
		assert_noop!(Broker::do_renew(1, 0), Error::<Test>::SoldOut);
		assert_eq!(balance(1), 800);

		// All leases should have ended
		assert!(Leases::<Test>::get().is_empty());

		// This intializes the fourth sale and the period 3.
		advance_sale_period();

		// Renew again
		assert_eq!(0, Broker::do_renew(1, new_core).unwrap());
		// Renew the task 2.
		assert_eq!(1, Broker::do_renew(1, 0).unwrap());
		assert_eq!(balance(1), 600);

		// This intializes the fifth sale and the period 4.
		advance_sale_period();

		assert_eq!(
			CoretimeTrace::get(),
			vec![
				(
					10,
					AssignCore {
						core: 0,
						begin: 12,
						assignment: vec![(Task(1), 57600)],
						end_hint: None
					}
				),
				(
					10,
					AssignCore {
						core: 1,
						begin: 12,
						assignment: vec![(Task(2), 57600)],
						end_hint: None
					}
				),
				(
					16,
					AssignCore {
						core: 0,
						begin: 18,
						assignment: vec![(Task(2), 57600)],
						end_hint: None
					}
				),
				(
					16,
					AssignCore {
						core: 1,
						begin: 18,
						assignment: vec![(Task(1), 57600)],
						end_hint: None
					}
				),
				(
					22,
					AssignCore {
						core: 0,
						begin: 24,
						assignment: vec![(Task(2), 57600)],
						end_hint: None,
					},
				),
				(
					22,
					AssignCore {
						core: 1,
						begin: 24,
						assignment: vec![(Task(1), 57600)],
						end_hint: None,
					},
				),
				(
					28,
					AssignCore {
						core: 0,
						begin: 30,
						assignment: vec![(Task(1), 57600)],
						end_hint: None,
					},
				),
				(
					28,
					AssignCore {
						core: 1,
						begin: 30,
						assignment: vec![(Task(2), 57600)],
						end_hint: None,
					},
				),
			]
		);
	});
}

#[test]
fn start_sales_sets_correct_core_count() {
	TestExt::new().endow(1, 1000).execute_with(|| {
		advance_to(1);

		Broker::do_set_lease(1, 100).unwrap();
		Broker::do_set_lease(2, 100).unwrap();
		Broker::do_set_lease(3, 100).unwrap();
		Broker::do_reserve(Schedule::truncate_from(vec![ScheduleItem {
			assignment: Pool,
			mask: CoreMask::complete(),
		}]))
		.unwrap();

		Broker::do_start_sales(5, 5).unwrap();

		System::assert_has_event(Event::<Test>::CoreCountRequested { core_count: 9 }.into());
	})
}