예제 #1
0
        public async Task BindAsync_ReturnsExpectedTriggerData()
        {
            ParameterInfo parameter  = GetType().GetMethod("TestTimerJob").GetParameters()[0];
            MethodInfo    methodInfo = (MethodInfo)parameter.Member;
            string        timerName  = string.Format("{0}.{1}", methodInfo.DeclaringType.FullName, methodInfo.Name);

            Mock <ScheduleMonitor> mockScheduleMonitor = new Mock <ScheduleMonitor>(MockBehavior.Strict);
            ScheduleStatus         status = new ScheduleStatus();

            mockScheduleMonitor.Setup(p => p.GetStatusAsync(timerName)).ReturnsAsync(status);

            TimerTriggerAttribute attribute = parameter.GetCustomAttribute <TimerTriggerAttribute>();
            TimersConfiguration   config    = new TimersConfiguration();

            config.ScheduleMonitor = mockScheduleMonitor.Object;
            TestTraceWriter     trace   = new TestTraceWriter();
            TimerTriggerBinding binding = new TimerTriggerBinding(parameter, attribute, config, trace);

            // when we bind to a non-TimerInfo (e.g. in a Dashboard invocation) a new
            // TimerInfo is created, with the ScheduleStatus populated
            FunctionBindingContext functionContext = new FunctionBindingContext(Guid.NewGuid(), CancellationToken.None, trace);
            ValueBindingContext    context         = new ValueBindingContext(functionContext, CancellationToken.None);
            TriggerData            triggerData     = (TriggerData)(await binding.BindAsync("", context));
            TimerInfo timerInfo = (TimerInfo)triggerData.ValueProvider.GetValue();

            Assert.Same(status, timerInfo.ScheduleStatus);

            // when we pass in a TimerInfo that is used
            TimerInfo expected = new TimerInfo(attribute.Schedule, status);

            triggerData = (TriggerData)(await binding.BindAsync(expected, context));
            timerInfo   = (TimerInfo)triggerData.ValueProvider.GetValue();
            Assert.Same(expected, timerInfo);
        }
예제 #2
0
        public void Create_ConstantSchedule_CreatesExpectedSchedule()
        {
            TimerTriggerAttribute attribute    = new TimerTriggerAttribute("00:00:15");
            INameResolver         nameResolver = new TestNameResolver();
            ConstantSchedule      schedule     = (ConstantSchedule)TimerSchedule.Create(attribute, nameResolver);

            Assert.False(attribute.UseMonitor);

            DateTime now            = new DateTime(2015, 5, 22, 9, 45, 00);
            DateTime nextOccurrence = schedule.GetNextOccurrence(now);

            Assert.Equal(new TimeSpan(0, 0, 15), nextOccurrence - now);

            // For schedules occuring on an interval greater than a minute, we expect
            // UseMonitor to be defaulted to true
            attribute = new TimerTriggerAttribute("01:00:00");
            schedule  = (ConstantSchedule)TimerSchedule.Create(attribute, nameResolver);
            Assert.True(attribute.UseMonitor);

            // verify that if UseMonitor is set explicitly, it is not overridden
            attribute            = new TimerTriggerAttribute("01:00:00");
            attribute.UseMonitor = false;
            schedule             = (ConstantSchedule)TimerSchedule.Create(attribute, nameResolver);
            Assert.False(attribute.UseMonitor);
        }
        public void Create_ConstantSchedule_CreatesExpectedSchedule()
        {
            TimerTriggerAttribute attribute    = new TimerTriggerAttribute("00:00:15");
            INameResolver         nameResolver = new TestNameResolver();
            ConstantSchedule      schedule     = (ConstantSchedule)TimerSchedule.Create(attribute, nameResolver, _logger);

            Assert.False(attribute.UseMonitor);
            var log = _loggerProvider.GetAllLogMessages().Single();

            Assert.Equal("UseMonitor changed to false based on schedule frequency.", log.FormattedMessage);
            Assert.Equal(LogLevel.Debug, log.Level);

            DateTime now            = new DateTime(2015, 5, 22, 9, 45, 00);
            DateTime nextOccurrence = schedule.GetNextOccurrence(now);

            Assert.Equal(new TimeSpan(0, 0, 15), nextOccurrence - now);

            // For schedules occuring on an interval greater than a minute, we expect
            // UseMonitor to be defaulted to true
            _loggerProvider.ClearAllLogMessages();
            attribute = new TimerTriggerAttribute("01:00:00");
            schedule  = (ConstantSchedule)TimerSchedule.Create(attribute, nameResolver, _logger);
            Assert.True(attribute.UseMonitor);
            Assert.Empty(_loggerProvider.GetAllLogMessages());

            // verify that if UseMonitor is set explicitly, it is not overridden
            attribute            = new TimerTriggerAttribute("01:00:00");
            attribute.UseMonitor = false;
            schedule             = (ConstantSchedule)TimerSchedule.Create(attribute, nameResolver, _logger);
            Assert.False(attribute.UseMonitor);
            Assert.Empty(_loggerProvider.GetAllLogMessages());
        }
 public TimerTriggerBinding(ParameterInfo parameter, TimerTriggerAttribute attribute, TimersConfiguration config)
 {
     _attribute = attribute;
     _parameter = parameter;
     _config = config;
     _bindingContract = CreateBindingDataContract();
 }
예제 #5
0
 public TimerTriggerBinding(ParameterInfo parameter, TimerTriggerAttribute attribute, TimersConfiguration config)
 {
     _attribute       = attribute;
     _parameter       = parameter;
     _config          = config;
     _bindingContract = CreateBindingDataContract();
 }
        public async Task Generate_EndToEnd()
        {
            // construct our TimerTrigger attribute ([TimerTrigger("00:00:02", RunOnStartup = true)])
            Collection <ParameterDescriptor> parameters = new Collection <ParameterDescriptor>();
            ParameterDescriptor    parameter            = new ParameterDescriptor("timerInfo", typeof(TimerInfo));
            ConstructorInfo        ctorInfo             = typeof(TimerTriggerAttribute).GetConstructor(new Type[] { typeof(string) });
            PropertyInfo           runOnStartupProperty = typeof(TimerTriggerAttribute).GetProperty("RunOnStartup");
            CustomAttributeBuilder attributeBuilder     = new CustomAttributeBuilder(
                ctorInfo,
                new object[] { "00:00:02" },
                new PropertyInfo[] { runOnStartupProperty },
                new object[] { true });

            parameter.CustomAttributes.Add(attributeBuilder);
            parameters.Add(parameter);

            // create the FunctionDefinition
            FunctionMetadata   metadata = new FunctionMetadata();
            TestInvoker        invoker  = new TestInvoker();
            FunctionDescriptor function = new FunctionDescriptor("TimerFunction", invoker, metadata, parameters, null, null, null);
            Collection <FunctionDescriptor> functions = new Collection <FunctionDescriptor>();

            functions.Add(function);

            // Get the Type Attributes (in this case, a TimeoutAttribute)
            ScriptHostConfiguration scriptConfig = new ScriptHostConfiguration();

            scriptConfig.FunctionTimeout = TimeSpan.FromMinutes(5);
            Collection <CustomAttributeBuilder> typeAttributes = new Collection <CustomAttributeBuilder>();

            // generate the Type
            Type functionType = FunctionGenerator.Generate("TestScriptHost", "TestFunctions", typeAttributes, functions);

            // verify the generated function
            MethodInfo            method           = functionType.GetMethod("TimerFunction");
            ParameterInfo         triggerParameter = method.GetParameters()[0];
            TimerTriggerAttribute triggerAttribute = triggerParameter.GetCustomAttribute <TimerTriggerAttribute>();

            Assert.NotNull(triggerAttribute);

            // start the JobHost which will start running the timer function
            JobHostConfiguration config = new JobHostConfiguration()
            {
                TypeLocator   = new TypeLocator(functionType),
                LoggerFactory = new LoggerFactory()
            };

            config.UseTimers();
            JobHost host = new JobHost(config);

            await host.StartAsync();

            await Task.Delay(3000);

            await host.StopAsync();

            // verify our custom invoker was called
            Assert.True(invoker.InvokeCount >= 2);
        }
예제 #7
0
        public void Constructor_ScheduleType()
        {
            TimerTriggerAttribute attribute = new TimerTriggerAttribute(typeof(DailySchedule));

            Assert.Null(attribute.ScheduleExpression);
            Assert.Equal(typeof(DailySchedule), attribute.ScheduleType);
            Assert.True(attribute.UseMonitor);
        }
예제 #8
0
        public void Constructor_ScheduleExpression()
        {
            TimerTriggerAttribute attribute = new TimerTriggerAttribute("00:00:15");

            Assert.Equal("00:00:15", attribute.ScheduleExpression);
            Assert.Null(attribute.ScheduleType);
            Assert.True(attribute.UseMonitor);
        }
예제 #9
0
        public void Create_InvalidSchedule()
        {
            TimerTriggerAttribute attribute = new TimerTriggerAttribute("invalid");
            ArgumentException     ex        = Assert.Throws <ArgumentException>(() =>
            {
                TimerSchedule.Create(attribute, new TestNameResolver());
            });

            Assert.Equal("The schedule expression 'invalid' was not recognized as a valid cron expression or timespan string.", ex.Message);
        }
        public TimerTriggerBinding(ParameterInfo parameter, TimerTriggerAttribute attribute, TimersConfiguration config, TraceWriter trace)
        {
            _attribute = attribute;
            _parameter = parameter;
            _config = config;
            _trace = trace;
            _bindingContract = CreateBindingDataContract();

            MethodInfo methodInfo = (MethodInfo)parameter.Member;
            _timerName = string.Format("{0}.{1}", methodInfo.DeclaringType.FullName, methodInfo.Name);
        }
예제 #11
0
        public void Constructor_CreatesCronSchedule()
        {
            TimerTriggerAttribute attribute = new TimerTriggerAttribute("*/15 * * * * *");

            Assert.Equal(typeof(CronSchedule), attribute.Schedule.GetType());

            DateTime now            = new DateTime(2015, 5, 22, 9, 45, 00);
            DateTime nextOccurrence = attribute.Schedule.GetNextOccurrence(now);

            Assert.Equal(new TimeSpan(0, 0, 15), nextOccurrence - now);
        }
예제 #12
0
        public void Constructor_CreatesConstantSchedule()
        {
            TimerTriggerAttribute attribute = new TimerTriggerAttribute("00:00:15");

            Assert.Equal(typeof(ConstantSchedule), attribute.Schedule.GetType());

            DateTime now            = DateTime.Now;
            DateTime nextOccurrence = attribute.Schedule.GetNextOccurrence(now);

            Assert.Equal(new TimeSpan(0, 0, 15), nextOccurrence - now);
        }
예제 #13
0
 public TimerListener(TimerTriggerAttribute attribute, TimerSchedule schedule, string timerName, TimersOptions options, ITriggeredFunctionExecutor executor, ILogger logger, ScheduleMonitor scheduleMonitor)
 {
     _attribute = attribute;
     _timerName = timerName;
     _options   = options;
     _executor  = executor;
     _logger    = logger;
     _cancellationTokenSource = new CancellationTokenSource();
     _schedule       = schedule;
     ScheduleMonitor = _attribute.UseMonitor ? scheduleMonitor : null;
 }
        public TimerListener(TimerTriggerAttribute attribute, string timerName, TimersConfiguration config, ITriggeredFunctionExecutor executor)
        {
            _attribute = attribute;
            _timerName = timerName;
            _config = config;
            _executor = executor;
            _cancellationTokenSource = new CancellationTokenSource();

            _schedule = _attribute.Schedule;
            _scheduleMonitor = _config.ScheduleMonitor;
        }
예제 #15
0
 public TimerListener(TimerTriggerAttribute attribute, string timerName, TimersConfiguration config, ITriggeredFunctionExecutor executor, TraceWriter trace)
 {
     _attribute = attribute;
     _timerName = timerName;
     _config    = config;
     _executor  = executor;
     _trace     = trace;
     _cancellationTokenSource = new CancellationTokenSource();
     _schedule       = _attribute.Schedule;
     ScheduleMonitor = _attribute.UseMonitor ? _config.ScheduleMonitor : null;
 }
        public TimerTriggerBinding(ParameterInfo parameter, TimerTriggerAttribute attribute, TimersConfiguration config, TraceWriter trace)
        {
            _attribute       = attribute;
            _parameter       = parameter;
            _config          = config;
            _trace           = trace;
            _bindingContract = CreateBindingDataContract();

            MethodInfo methodInfo = (MethodInfo)parameter.Member;

            _timerName = string.Format("{0}.{1}", methodInfo.DeclaringType.FullName, methodInfo.Name);
        }
        public TimerTriggerBinding(ParameterInfo parameter, TimerTriggerAttribute attribute, TimerSchedule schedule, TimersOptions options, ILogger logger, ScheduleMonitor scheduleMonitor)
        {
            _attribute       = attribute;
            _schedule        = schedule;
            _parameter       = parameter;
            _options         = options;
            _logger          = logger;
            _scheduleMonitor = scheduleMonitor;
            _bindingContract = CreateBindingDataContract();

            MethodInfo methodInfo = (MethodInfo)parameter.Member;

            _timerName = string.Format("{0}.{1}", methodInfo.DeclaringType.FullName, methodInfo.Name);
        }
예제 #18
0
        public void Create_CustomSchedule_CreatesExpectedSchedule()
        {
            TimerTriggerAttribute attribute    = new TimerTriggerAttribute(typeof(CustomSchedule));
            INameResolver         nameResolver = new TestNameResolver();
            CustomSchedule        schedule     = (CustomSchedule)TimerSchedule.Create(attribute, nameResolver);

            Assert.NotNull(schedule);
            Assert.True(attribute.UseMonitor);

            // verify that if UseMonitor is set explicitly, it is not overridden
            attribute            = new TimerTriggerAttribute(typeof(CustomSchedule));
            attribute.UseMonitor = false;
            schedule             = (CustomSchedule)TimerSchedule.Create(attribute, nameResolver);
            Assert.False(attribute.UseMonitor);
        }
예제 #19
0
        public void Create_UsesNameResolver()
        {
            TimerTriggerAttribute attribute    = new TimerTriggerAttribute("%test_schedule%");
            TestNameResolver      nameResolver = new TestNameResolver();

            nameResolver.Values.Add("test_schedule", "*/15 * * * * *");
            CronSchedule schedule = (CronSchedule)TimerSchedule.Create(attribute, nameResolver);

            Assert.False(attribute.UseMonitor);

            DateTime now            = new DateTime(2015, 5, 22, 9, 45, 00);
            DateTime nextOccurrence = schedule.GetNextOccurrence(now);

            Assert.Equal(new TimeSpan(0, 0, 15), nextOccurrence - now);
        }
예제 #20
0
        public void GenerateTimerTriggerFunction()
        {
            BindingMetadata trigger = BindingMetadata.Create(new JObject
            {
                { "type", "TimerTrigger" },
                { "name", "timerInfo" },
                { "schedule", "* * * * * *" },
                { "runOnStartup", true },
                { "direction", "in" }
            });
            MethodInfo method = GenerateMethod(trigger);

            VerifyCommonProperties(method);

            // verify trigger parameter
            ParameterInfo parameter = method.GetParameters()[0];

            Assert.Equal("timerInfo", parameter.Name);
            Assert.Equal(typeof(TimerInfo), parameter.ParameterType);
            TimerTriggerAttribute attribute = parameter.GetCustomAttribute <TimerTriggerAttribute>();

            Assert.Equal("* * * * * *", attribute.ScheduleExpression);
            Assert.True(attribute.UseMonitor);
            Assert.True(attribute.RunOnStartup);

            trigger = BindingMetadata.Create(new JObject
            {
                { "type", "TimerTrigger" },
                { "name", "timerInfo" },
                { "schedule", "* * * * * *" },
                { "useMonitor", false },
                { "direction", "in" }
            });
            method = GenerateMethod(trigger);

            VerifyCommonProperties(method);

            // verify trigger parameter
            parameter = method.GetParameters()[0];
            Assert.Equal("timerInfo", parameter.Name);
            Assert.Equal(typeof(TimerInfo), parameter.ParameterType);
            attribute = parameter.GetCustomAttribute <TimerTriggerAttribute>();
            Assert.Equal("* * * * * *", attribute.ScheduleExpression);
            Assert.False(attribute.UseMonitor);
            Assert.False(attribute.RunOnStartup);
        }
        private void VerifyConstantSchedule(string expression, TimeSpan expectedInterval)
        {
            Assert.True(TimeSpan.TryParse(expression, out TimeSpan timeSpan));
            Assert.Equal(timeSpan, expectedInterval);

            TimerTriggerAttribute attribute    = new TimerTriggerAttribute(expression);
            INameResolver         nameResolver = new TestNameResolver();
            ConstantSchedule      schedule     = (ConstantSchedule)TimerSchedule.Create(attribute, nameResolver, _logger);

            DateTime now         = new DateTime(2015, 5, 22, 9, 45, 00);
            var      occurrences = schedule.GetNextOccurrences(5, now);

            for (int i = 0; i < 4; i++)
            {
                var delta = occurrences.ElementAt(i + 1) - occurrences.ElementAt(i);
                Assert.Equal(expectedInterval, delta);
            }
        }
        private void CreateTestListener(string expression, bool useMonitor = true)
        {
            _attribute            = new TimerTriggerAttribute(expression);
            _attribute.UseMonitor = useMonitor;
            _config = new TimersConfiguration();
            _mockScheduleMonitor    = new Mock <ScheduleMonitor>(MockBehavior.Strict);
            _config.ScheduleMonitor = _mockScheduleMonitor.Object;
            _mockTriggerExecutor    = new Mock <ITriggeredFunctionExecutor>(MockBehavior.Strict);
            FunctionResult result = new FunctionResult(true);

            _mockTriggerExecutor.Setup(p => p.TryExecuteAsync(It.IsAny <TriggeredFunctionData>(), It.IsAny <CancellationToken>()))
            .Callback <TriggeredFunctionData, CancellationToken>((mockFunctionData, mockToken) =>
            {
                _triggeredFunctionData = mockFunctionData;
            })
            .Returns(Task.FromResult(result));
            _listener = new TimerListener(_attribute, _testTimerName, _config, _mockTriggerExecutor.Object);
        }
        private void CreateTestListener(string expression, bool useMonitor = true, Action functionAction = null)
        {
            _attribute            = new TimerTriggerAttribute(expression);
            _schedule             = TimerSchedule.Create(_attribute, new TestNameResolver());
            _attribute.UseMonitor = useMonitor;
            _options             = new TimersOptions();
            _mockScheduleMonitor = new Mock <ScheduleMonitor>(MockBehavior.Strict);
            _mockTriggerExecutor = new Mock <ITriggeredFunctionExecutor>(MockBehavior.Strict);
            FunctionResult result = new FunctionResult(true);

            _mockTriggerExecutor.Setup(p => p.TryExecuteAsync(It.IsAny <TriggeredFunctionData>(), It.IsAny <CancellationToken>()))
            .Callback <TriggeredFunctionData, CancellationToken>((mockFunctionData, mockToken) =>
            {
                _triggeredFunctionData = mockFunctionData;
                functionAction?.Invoke();
            })
            .Returns(Task.FromResult(result));
            _logger   = new TestLogger(null);
            _listener = new TimerListener(_attribute, _schedule, _testTimerName, _options, _mockTriggerExecutor.Object, _logger, _mockScheduleMonitor.Object);
        }
        public Task <ITriggerBinding> TryCreateAsync(TriggerBindingProviderContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            ParameterInfo         parameter             = context.Parameter;
            TimerTriggerAttribute timerTriggerAttribute = parameter.GetCustomAttribute <TimerTriggerAttribute>(inherit: false);

            if (timerTriggerAttribute == null)
            {
                return(Task.FromResult <ITriggerBinding>(null));
            }

            if (parameter.ParameterType != typeof(TimerInfo))
            {
                throw new InvalidOperationException(string.Format("Can't bind TimerTriggerAttribute to type '{0}'.", parameter.ParameterType));
            }

            return(Task.FromResult <ITriggerBinding>(new TimerTriggerBinding(parameter, timerTriggerAttribute, _config, _trace)));
        }
예제 #25
0
        public void GenerateTimerTriggerFunction()
        {
            TimerBindingMetadata trigger = new TimerBindingMetadata
            {
                Type         = BindingType.TimerTrigger,
                Schedule     = "* * * * * *",
                RunOnStartup = true
            };
            MethodInfo method = GenerateMethod(trigger);

            VerifyCommonProperties(method);

            // verify trigger parameter
            ParameterInfo parameter = method.GetParameters()[0];

            Assert.Equal("input", parameter.Name);
            Assert.Equal(typeof(TimerInfo), parameter.ParameterType);
            TimerTriggerAttribute attribute = parameter.GetCustomAttribute <TimerTriggerAttribute>();

            Assert.Equal("* * * * * *", attribute.ScheduleExpression);
            Assert.True(attribute.RunOnStartup);
        }
        public async Task BindAsync_ReturnsExpectedTriggerData()
        {
            ParameterInfo parameter  = GetType().GetMethod("TestTimerJob").GetParameters()[0];
            MethodInfo    methodInfo = (MethodInfo)parameter.Member;
            string        timerName  = string.Format("{0}.{1}", methodInfo.DeclaringType.FullName, methodInfo.Name);

            Mock <ScheduleMonitor> mockScheduleMonitor = new Mock <ScheduleMonitor>(MockBehavior.Strict);
            ScheduleStatus         status = new ScheduleStatus();

            mockScheduleMonitor.Setup(p => p.GetStatusAsync(timerName)).ReturnsAsync(status);

            TimerTriggerAttribute attribute    = parameter.GetCustomAttribute <TimerTriggerAttribute>();
            INameResolver         nameResolver = new TestNameResolver();
            TimerSchedule         schedule     = TimerSchedule.Create(attribute, nameResolver);
            TimersOptions         options      = new TimersOptions();

            ILoggerFactory loggerFactory = new LoggerFactory();

            loggerFactory.AddProvider(new TestLoggerProvider());

            TimerTriggerBinding binding = new TimerTriggerBinding(parameter, attribute, schedule, options, loggerFactory.CreateLogger("Test"), mockScheduleMonitor.Object);

            // when we bind to a non-TimerInfo (e.g. in a Dashboard invocation) a new
            // TimerInfo is created, with the ScheduleStatus populated
            FunctionBindingContext functionContext = new FunctionBindingContext(Guid.NewGuid(), CancellationToken.None);
            ValueBindingContext    context         = new ValueBindingContext(functionContext, CancellationToken.None);
            TriggerData            triggerData     = (TriggerData)(await binding.BindAsync(string.Empty, context));
            TimerInfo timerInfo = (TimerInfo)(await triggerData.ValueProvider.GetValueAsync());

            Assert.Same(status, timerInfo.ScheduleStatus);

            // when we pass in a TimerInfo that is used
            TimerInfo expected = new TimerInfo(schedule, status);

            triggerData = (TriggerData)(await binding.BindAsync(expected, context));
            timerInfo   = (TimerInfo)(await triggerData.ValueProvider.GetValueAsync());
            Assert.Same(expected, timerInfo);
        }
예제 #27
0
        public async Task Generate_EndToEnd()
        {
            // construct our TimerTrigger attribute ([TimerTrigger("00:00:02", RunOnStartup = true)])
            Collection <ParameterDescriptor> parameters = new Collection <ParameterDescriptor>();

            ParameterDescriptor    parameter            = new ParameterDescriptor("timerInfo", typeof(TimerInfo));
            ConstructorInfo        ctorInfo             = typeof(TimerTriggerAttribute).GetConstructor(new Type[] { typeof(string) });
            PropertyInfo           runOnStartupProperty = typeof(TimerTriggerAttribute).GetProperty("RunOnStartup");
            CustomAttributeBuilder attributeBuilder     = new CustomAttributeBuilder(
                ctorInfo,
                new object[] { "00:00:02" },
                new PropertyInfo[] { runOnStartupProperty },
                new object[] { true });

            parameter.CustomAttributes.Add(attributeBuilder);
            parameters.Add(parameter);

            // create the FunctionDefinition
            FunctionMetadata   metadata = new FunctionMetadata();
            TestInvoker        invoker  = new TestInvoker();
            FunctionDescriptor function = new FunctionDescriptor("TimerFunction", invoker, metadata, parameters, null, null, null);
            Collection <FunctionDescriptor> functions = new Collection <FunctionDescriptor>();

            functions.Add(function);

            // Get the Type Attributes (in this case, a TimeoutAttribute)
            ScriptJobHostOptions scriptConfig = new ScriptJobHostOptions();

            scriptConfig.FunctionTimeout = TimeSpan.FromMinutes(5);
            Collection <CustomAttributeBuilder> typeAttributes = new Collection <CustomAttributeBuilder>();

            // generate the Type
            Type functionType = FunctionGenerator.Generate("TestScriptHost", "TestFunctions", typeAttributes, functions);

            // verify the generated function
            MethodInfo            method           = functionType.GetMethod("TimerFunction");
            ParameterInfo         triggerParameter = method.GetParameters()[0];
            TimerTriggerAttribute triggerAttribute = triggerParameter.GetCustomAttribute <TimerTriggerAttribute>();

            Assert.NotNull(triggerAttribute);

            // start the JobHost which will start running the timer function
            var builder = new HostBuilder()
                          .ConfigureWebJobs(b =>
            {
                b.AddTimers()
                .AddAzureStorageCoreServices();
            })
                          .ConfigureServices(s =>
            {
                s.AddSingleton <ITypeLocator>(new TestTypeLocator(functionType));
                s.AddSingleton <ILoggerFactory>(new LoggerFactory());

                TestHelpers.AddTestAzureBlobStorageProvider(s, TestHelpers.GetTestConfiguration());
                TestHostBuilderExtensions.AddMockedSingleton <IScriptHostManager>(s);
            });

            using (var host = builder.Build())
            {
                await host.StartAsync();

                await Task.Delay(3000);

                await host.StopAsync();
            }

            // verify our custom invoker was called
            Assert.True(invoker.InvokeCount >= 2);
        }