public void No_Resource_Can_Be_Used_Twice() { // Create the component under test var componentUnderTest = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase ); var resource = ResourceMock.Create( "a" ); // Register devices componentUnderTest.Add( resource ); componentUnderTest.Add( resource ); }
public void Individual_Decryption_Counter_Must_Not_Be_Negative() { // Create the component under test var componentUnderTest = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase ); // Register device componentUnderTest.Add( ResourceMock.Create( "a" ).SetEncryptionLimit( -1 ) ); }
public void A_Resource_Must_Not_Be_Null() { // Create the component under test var componentUnderTest = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase ); // Register a null resource componentUnderTest.Add( default( IScheduleResource ) ); }
public void Can_Add_Multiple_Resources() { // Create the component under test var componentUnderTest = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase ); // Register devices for (int i = 0; i < 100; i++) componentUnderTest.Add( ResourceMock.Create( i.ToString( "00" ) ) ); }
/// <summary> /// Registriert diese Aufzeichnung in einer Planungsinstanz. /// </summary> /// <param name="scheduler">Die zu verwendende Planungsinstanz.</param> /// <param name="job">Der zugehörige Auftrag.</param> /// <param name="devices">Die Liste der Geräte, auf denen die Aufzeichnung ausgeführt werden darf.</param> /// <param name="findSource">Dient zum Prüfen einer Quelle.</param> /// <param name="disabled">Alle deaktivierten Aufträge.</param> /// <param name="context">Die aktuelle Planungsumgebung.</param> /// <exception cref="ArgumentNullException">Es wurden nicht alle Parameter angegeben.</exception> public void AddToScheduler( RecordingScheduler scheduler, VCRJob job, IScheduleResource[] devices, Func<SourceSelection, SourceSelection> findSource, Func<Guid, bool> disabled, PlanContext context ) { // Validate if (scheduler == null) throw new ArgumentNullException( nameof( scheduler ) ); if (job == null) throw new ArgumentNullException( nameof( job ) ); if (findSource == null) throw new ArgumentNullException( nameof( findSource ) ); // Let VCR.NET choose a profile to do the work if (job.AutomaticResourceSelection) devices = null; // Create the source selection var persistedSource = Source ?? job.Source; var selection = findSource( persistedSource ); // Station no longer available if (selection == null) if (persistedSource != null) selection = new SourceSelection { DisplayName = persistedSource.DisplayName, ProfileName = persistedSource.ProfileName, Location = persistedSource.Location, Group = persistedSource.Group, Source = new Station { TransportStream = persistedSource.Source?.TransportStream ?? 0, Network = persistedSource.Source?.Network ?? 0, Service = persistedSource.Source?.Service ?? 0, Name = persistedSource.DisplayName, }, }; // See if we are allowed to process var identifier = UniqueID.Value; if (disabled != null) if (disabled( identifier )) return; // Load all var name = string.IsNullOrEmpty( Name ) ? job.Name : $"{job.Name} ({Name})"; var source = ProfileScheduleResource.CreateSource( selection ); var duration = TimeSpan.FromMinutes( Duration ); var noStartBefore = NoStartBefore; var start = FirstStart; // Check repetition var repeat = CreateRepeatPattern(); if (repeat == null) { // Only if not being recorded if (!noStartBefore.HasValue) scheduler.Add( RecordingDefinition.Create( this, name, identifier, devices, source, start, duration ) ); } else { // See if we have to adjust the start day if (noStartBefore.HasValue) { // Attach to the limit - actually we shift it a bit further assuming that we did have no large exception towards the past and the duration is moderate var startAfter = noStartBefore.Value.AddHours( 12 ); var startAfterDay = startAfter.ToLocalTime().Date; // Localize the start time var startTime = start.ToLocalTime().TimeOfDay; // First adjust start = (startAfterDay + startTime).ToUniversalTime(); // One more day if (start < startAfter) start = (startAfterDay.AddDays( 1 ) + startTime).ToUniversalTime(); } // Read the rest var exceptions = Exceptions.Select( e => e.ToPlanException( duration ) ).ToArray(); var endDay = LastDay.GetValueOrDefault( MaxMovableDay ); // A bit more complex if (start.Date <= endDay.Date) scheduler.Add( RecordingDefinition.Create( this, name, identifier, devices, source, start, duration, endDay, repeat ), exceptions ); } }
public void The_Number_Of_Recordings_Per_Plan_Is_Limited() { // Create component under test var cut = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase ) { FreeTVDevice }; // Add all plans for (int i = 0; i <= RecordingScheduler.MaximumRecordingsInPlan; i++) { // Create recording var plan = RecordingDefinition.Create( false, "test", Guid.NewGuid(), null, SourceMock.Source1Group1Free, TimeBias.AddHours( i ), TimeSpan.FromMinutes( 90 ) ); // Add it cut.Add( plan ); } // Resolve var schedules = cut.GetSchedules( TimeBias ).ToArray(); // Validate Assert.AreEqual( RecordingScheduler.MaximumRecordingsInPlan + 1, (uint) schedules.Length, "Schedules" ); // Check all for (int i = 0; i < RecordingScheduler.MaximumRecordingsInPlan; i++) { // Load var schedule = schedules[i]; // Validate Assert.AreSame( FreeTVDevice, schedule.Resource, "Resource {0}", i ); Assert.AreEqual( TimeBias.AddHours( i ), schedule.Time.Start, "Start {0}", i ); Assert.AreEqual( TimeSpan.FromMinutes( 90 ), schedule.Time.Duration, "Duration {0}", i ); Assert.IsFalse( schedule.StartsLate, "Late {0}", i ); } // Load the last var last = schedules[RecordingScheduler.MaximumRecordingsInPlan]; // Validate - internal planning is not touched Assert.AreSame( FreeTVDevice, last.Resource, "Resource" ); Assert.AreEqual( TimeBias.AddHours( RecordingScheduler.MaximumRecordingsInPlan ), last.Time.Start, "Start" ); Assert.AreEqual( TimeSpan.FromMinutes( 90 ), last.Time.Duration, "Duration" ); Assert.IsFalse( last.StartsLate, "Late" ); }
public void Performance_Of_A_Real_Life_Scenario() { // Get the path to the test directories var jobDirectory = @"*TBD*"; var profileDirectory = Environment.ExpandEnvironmentVariables( @"%AllUsersProfile%\DVBNETProfiles" ); // Helper - will be reused var job = new XmlDocument(); // Sources var sourceMap = new Dictionary<string, SourceMock>( StringComparer.InvariantCultureIgnoreCase ); var profileMap = new Dictionary<string, XmlDocument>( StringComparer.InvariantCultureIgnoreCase ); var groupMap = new Dictionary<object, Guid>(); var plan = new List<IRecordingDefinition>(); // Process all job files foreach (var jobPath in Directory.EnumerateFiles( jobDirectory, "*.j39" )) { // Load to document job.Load( jobPath ); // Common data var autoSelect = bool.Parse( job.SelectSingleNode( "VCRJob/AutomaticResourceSelection" ).InnerText ); var defaultSource = job.SelectSingleNode( "VCRJob/Source" ); var profileName = defaultSource.InnerText.Substring( defaultSource.InnerText.LastIndexOf( '@' ) + 1 ); var jobName = job.SelectSingleNode( "VCRJob/Name" ).InnerText; // Load the profile once XmlDocument profile; if (!profileMap.TryGetValue( profileName, out profile )) { // Create profile = new XmlDocument(); // Load profile.Load( Path.Combine( profileDirectory, profileName + ".dnp" ) ); // Remapping for (; ; ) { // Create a new namespace table var namespaces = new XmlNamespaceManager( profile.NameTable ); // Add us namespaces.AddNamespace( "dvbnet", "http://psimarron.net/DVBNET/Profiles" ); // Check for remap var loadFrom = profile.SelectSingleNode( "//dvbnet:UseSourcesFrom", namespaces ); if (loadFrom == null) break; // Load the name var refProfileName = loadFrom.InnerText; if (string.IsNullOrEmpty( refProfileName )) break; // Reload if (profileMap.TryGetValue( refProfileName, out profile )) continue; // Create profile = new XmlDocument(); // Load profile.Load( Path.Combine( profileDirectory, refProfileName + ".dnp" ) ); // Remember profileMap.Add( refProfileName, profile ); } // Remember profileMap.Add( profileName, profile ); } // Create a new namespace table var profileNamespaces = new XmlNamespaceManager( profile.NameTable ); // Add us profileNamespaces.AddNamespace( "dvbnet", "http://psimarron.net/DVBNET/Profiles" ); // Validate Assert.IsTrue( autoSelect, "Auto Select" ); // Process all schedules foreach (XmlElement schedule in job.SelectNodes( "VCRJob/Schedule" )) { // Extract data var repeat = schedule.SelectSingleNode( "Days" ).InnerText.Split( ' ' ).Where( d => !string.IsNullOrEmpty( d ) ).Select( d => (DayOfWeek) Enum.Parse( typeof( DayOfWeek ), d ) ).ToArray(); var firstStart = DateTime.Parse( schedule.SelectSingleNode( "FirstStart" ).InnerText, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind ); var lastDay = DateTime.Parse( schedule.SelectSingleNode( "LastDay" ).InnerText, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind ); var duration = TimeSpan.FromMinutes( uint.Parse( schedule.SelectSingleNode( "Duration" ).InnerText ) ); var source = (XmlElement) (schedule.SelectSingleNode( "Source" ) ?? defaultSource); var id = Guid.Parse( schedule.SelectSingleNode( "UniqueID" ).InnerText ); var scheduleName = schedule.SelectSingleNode( "Name" ).InnerText; var sourceName = source.GetAttribute( "name" ); // Find the real source SourceMock realSource; if (!sourceMap.TryGetValue( sourceName, out realSource )) { // Split name var split = sourceName.LastIndexOf( '[' ); var stationName = sourceName.Substring( 0, split ).TrimEnd(); var providerName = sourceName.Substring( split + 1 ).TrimEnd( ']' ); // Locate the station var station = (XmlElement) profile.SelectSingleNode( "//dvbnet:Station[@name='" + stationName + "' and @provider='" + providerName + "']", profileNamespaces ); var encrypted = bool.Parse( station.GetAttribute( "scrambled" ) ); var group = station.ParentNode; // Unique group identifier Guid groupIdentifier; if (!groupMap.TryGetValue( group, out groupIdentifier )) groupMap.Add( group, groupIdentifier = Guid.NewGuid() ); // Create the source entry sourceMap.Add( sourceName, realSource = SourceMock.Create( sourceName, groupIdentifier, encrypted ) ); } // Get the full name if (string.IsNullOrEmpty( scheduleName )) scheduleName = jobName; else if (!string.IsNullOrEmpty( jobName )) scheduleName = jobName + " (" + scheduleName + ")"; // Create the request if (repeat.Length > 0) plan.Add( RecordingDefinition.Create( schedule, scheduleName, id, null, realSource, firstStart, duration, lastDay, repeat ) ); else plan.Add( RecordingDefinition.Create( schedule, scheduleName, id, null, realSource, firstStart, duration ) ); } } // Create component under test var componentUnderTest = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase ); var sources = sourceMap.Values.ToArray(); // Add resources - hard coded, should be taken from configuration in some future version for (var i = 0; i++ < 4; ) componentUnderTest.Add( ResourceMock.Create( "duo" + i.ToString( "0" ), sources ).SetEncryptionLimit( 1 ) ); // Add the plan foreach (var recording in plan) componentUnderTest.Add( recording ); // Process var refTime = new DateTime( 2013, 10, 26, 12, 0, 0, DateTimeKind.Utc ); var timer = Stopwatch.StartNew(); var all = componentUnderTest.GetSchedules( refTime ).Take( 1000 ).ToArray(); var elapsed = timer.Elapsed; // Report Console.WriteLine( "{0:N3}ms", elapsed.TotalMilliseconds ); // Validate Assert.AreEqual( 1000, all.Length, "#jobs" ); }
public void Can_Not_Add_A_Null_Plan_Item() { // Create component under test var componentUnderTest = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase ); // Add componentUnderTest.Add( default( IRecordingDefinition ) ); }
public void Can_Handle_Very_Long_Recordings_With_Multiple_Devices() { // All sources var sources = Enumerable .Range( 0, 100 ) .Select( i => SourceMock.Create( "S" + i.ToString( "00" ) ) ) .ToArray(); // Create environment var device1 = ResourceMock.Create( "D1", sources ); var device2 = ResourceMock.Create( "D2", sources ); var device3 = ResourceMock.Create( "D3", sources ); var device4 = ResourceMock.Create( "D4", sources ); // Create the plan var allDays = new[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday, DayOfWeek.Saturday, DayOfWeek.Sunday }; var refTime = new DateTime( 2013, 12, 1 ); var plan1 = RecordingDefinition.Create( false, "test1", Guid.NewGuid(), null, sources[0], refTime, TimeSpan.FromHours( 23 ), refTime.AddYears( 100 ), allDays ); var plan2 = RecordingDefinition.Create( false, "test2", Guid.NewGuid(), null, sources[1], refTime.AddHours( 22 ), TimeSpan.FromHours( 4 ), refTime.AddYears( 100 ), allDays ); // Create the component under test var cut = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase ) { device1, device2, device3, device4, plan1, plan2 }; // Other foreach (var plan in Enumerable .Range( 2, 10 ) .Select( i => RecordingDefinition.Create( false, "test" + i.ToString( "00" ), Guid.NewGuid(), null, sources[i], refTime.AddMinutes( 5 * i ), TimeSpan.FromMinutes( 6 ), refTime.AddYears( 100 ), allDays ) )) cut.Add( plan ); // Get the schedule var schedules = cut.GetSchedules( refTime.AddHours( -1 ) ).Take( 500 ).ToArray(); // Validate Assert.AreEqual( 500, schedules.Length, "#count" ); Assert.IsFalse( schedules.Any( schedule => schedule.StartsLate ), "late!" ); // Dump plan foreach (var schedule in schedules) Console.WriteLine( schedule ); }
public void There_Can_Be_Only_One_Exception_Per_Date() { // Create bad exception var exception1 = new PlanException { ExceptionDate = DateTime.Now.Date }; var exception2 = new PlanException { ExceptionDate = exception1.ExceptionDate }; // Create component under test var componentUnderTest = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase ); // Add componentUnderTest.Add( ResourceMock.Create( "r1", SourceMock.Create( "s1" ) ) ); componentUnderTest.Add( RecordingDefinition.Create( false, "test", Guid.NewGuid(), null, SourceMock.Create( "s1" ), DateTime.UtcNow, TimeSpan.FromMinutes( 12 ) ), exception1, exception2 ); }
public void Exceptions_Must_Be_Defined_On_A_Full_Date() { // Create bad exception var exception = new PlanException { ExceptionDate = DateTime.Now.Date.AddMinutes( 12 ) }; // Create component under test var componentUnderTest = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase ); // Add componentUnderTest.Add( ResourceMock.Create( "r1", SourceMock.Create( "s1" ) ) ); componentUnderTest.Add( RecordingDefinition.Create( false, "test", Guid.NewGuid(), null, SourceMock.Create( "s1" ), DateTime.UtcNow, TimeSpan.FromMinutes( 12 ) ), exception ); }
public void Encrypted_Source_Of_A_Plan_Item_Is_Supported_By_Resource() { // Create component under test var componentUnderTest = new RecordingScheduler( StringComparer.InvariantCultureIgnoreCase ); // Add componentUnderTest.Add( ResourceMock.Create( "r1", SourceMock.Create( "s1" ) ).SetEncryptionLimit( 1 ) ); componentUnderTest.Add( RecordingDefinition.Create( false, "test", Guid.NewGuid(), null, SourceMock.Create( "s1", true ), DateTime.UtcNow, TimeSpan.FromMinutes( 12 ) ) ); }