<commit_before>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package resolver_test

import (
	"errors"
	"time"

	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"
	"gopkg.in/juju/charm.v5"
	"launchpad.net/tomb"

	"github.com/juju/juju/testing"
	coretesting "github.com/juju/juju/testing"
	"github.com/juju/juju/worker/uniter/operation"
	"github.com/juju/juju/worker/uniter/remotestate"
	"github.com/juju/juju/worker/uniter/resolver"
)

type LoopSuite struct {
	testing.BaseSuite

	resolver  resolver.Resolver
	watcher   *mockRemoteStateWatcher
	opFactory *mockOpFactory
	executor  *mockOpExecutor
	charmURL  *charm.URL
	dying     chan struct{}
	onIdle    func() error
}

var _ = gc.Suite(&LoopSuite{})

func (s *LoopSuite) SetUpTest(c *gc.C) {
	s.BaseSuite.SetUpTest(c)
	s.resolver = resolver.ResolverFunc(func(resolver.LocalState, remotestate.Snapshot, operation.Factory) (operation.Operation, error) {
		return nil, resolver.ErrNoOperation
	})
	s.watcher = &mockRemoteStateWatcher{
		changes: make(chan struct{}, 1),
	}
	s.opFactory = &mockOpFactory{}
	s.executor = &mockOpExecutor{}
	s.charmURL = charm.MustParseURL("cs:trusty/mysql")
	s.dying = make(chan struct{})
}

func (s *LoopSuite) loop() (resolver.LocalState, error) {
	return resolver.Loop(resolver.LoopConfig{
		Resolver: s.resolver,
		Factory:  s.opFactory,
		Watcher:  s.watcher,
		Executor: s.executor,
		UpdateStatusChannel: func() <-chan time.Time {
			// TODO(axw) test update status channel
			return nil
		},
		CharmURL:       s.charmURL,
		Dying:          s.dying,
		OnIdle:         s.onIdle,
		CharmDirLocker: &mockCharmDirLocker{},
	})
}

func (s *LoopSuite) TestDying(c *gc.C) {
	close(s.dying)
	_, err := s.loop()
	c.Assert(err, gc.Equals, tomb.ErrDying)
}

func (s *LoopSuite) TestOnIdle(c *gc.C) {
	onIdleCh := make(chan interface{}, 1)
	s.onIdle = func() error {
		onIdleCh <- nil
		return nil
	}

	done := make(chan interface{}, 1)
	go func() {
		_, err := s.loop()
		done <- err
	}()

	waitChannel(c, onIdleCh, "waiting for onIdle")
	s.watcher.changes <- struct{}{}
	waitChannel(c, onIdleCh, "waiting for onIdle")
	close(s.dying)

	err := waitChannel(c, done, "waiting for loop to exit")
	c.Assert(err, gc.Equals, tomb.ErrDying)

	select {
	case <-onIdleCh:
		c.Fatal("unexpected onIdle call")
	default:
	}
}

func (s *LoopSuite) TestOnIdleError(c *gc.C) {
	s.onIdle = func() error {
		return errors.New("onIdle failed")
	}
	close(s.dying)
	_, err := s.loop()
	c.Assert(err, gc.ErrorMatches, "onIdle failed")
}

func (s *LoopSuite) TestErrWaitingNoOnIdle(c *gc.C) {
	var onIdleCalled bool
	s.onIdle = func() error {
		onIdleCalled = true
		return nil
	}
	s.resolver = resolver.ResolverFunc(func(
		_ resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		return nil, resolver.ErrWaiting
	})
	close(s.dying)
	_, err := s.loop()
	c.Assert(err, gc.Equals, tomb.ErrDying)
	c.Assert(onIdleCalled, jc.IsFalse)
}

func (s *LoopSuite) TestInitialFinalLocalState(c *gc.C) {
	var local resolver.LocalState
	s.resolver = resolver.ResolverFunc(func(
		l resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		local = l
		return nil, resolver.ErrNoOperation
	})

	close(s.dying)
	lastLocal, err := s.loop()
	c.Assert(err, gc.Equals, tomb.ErrDying)
	c.Assert(local, jc.DeepEquals, resolver.LocalState{
		CharmURL:         s.charmURL,
		CompletedActions: map[string]struct{}{},
	})
	c.Assert(lastLocal, jc.DeepEquals, local)
}

func (s *LoopSuite) TestLoop(c *gc.C) {
	var resolverCalls int
	theOp := &mockOp{}
	s.resolver = resolver.ResolverFunc(func(
		_ resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		resolverCalls++
		switch resolverCalls {
		// On the first call, return an operation.
		case 1:
			return theOp, nil
		// On the second call, simulate having
		// no operations to perform, at which
		// point we'll wait for a remote state
		// change.
		case 2:
			s.watcher.changes <- struct{}{}
			break
		// On the third call, kill the loop.
		case 3:
			close(s.dying)
			break
		}
		return nil, resolver.ErrNoOperation
	})

	_, err := s.loop()
	c.Assert(err, gc.Equals, tomb.ErrDying)
	c.Assert(resolverCalls, gc.Equals, 3)
	s.executor.CheckCallNames(c, "State", "Run", "State", "State")
	c.Assert(s.executor.Calls()[1].Args, jc.SameContents, []interface{}{theOp})
}

func (s *LoopSuite) TestRunFails(c *gc.C) {
	s.executor.SetErrors(errors.New("Run fails"))
	s.resolver = resolver.ResolverFunc(func(
		_ resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		return mockOp{}, nil
	})
	_, err := s.loop()
	c.Assert(err, gc.ErrorMatches, "Run fails")
}

func (s *LoopSuite) TestNextOpFails(c *gc.C) {
	s.resolver = resolver.ResolverFunc(func(
		_ resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		return nil, errors.New("NextOp fails")
	})
	_, err := s.loop()<commit_msg>Update stub tests to take into account extra local state lookup.<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package resolver_test

import (
	"errors"
	"time"

	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"
	"gopkg.in/juju/charm.v5"
	"launchpad.net/tomb"

	"github.com/juju/juju/testing"
	coretesting "github.com/juju/juju/testing"
	"github.com/juju/juju/worker/uniter/operation"
	"github.com/juju/juju/worker/uniter/remotestate"
	"github.com/juju/juju/worker/uniter/resolver"
)

type LoopSuite struct {
	testing.BaseSuite

	resolver  resolver.Resolver
	watcher   *mockRemoteStateWatcher
	opFactory *mockOpFactory
	executor  *mockOpExecutor
	charmURL  *charm.URL
	dying     chan struct{}
	onIdle    func() error
}

var _ = gc.Suite(&LoopSuite{})

func (s *LoopSuite) SetUpTest(c *gc.C) {
	s.BaseSuite.SetUpTest(c)
	s.resolver = resolver.ResolverFunc(func(resolver.LocalState, remotestate.Snapshot, operation.Factory) (operation.Operation, error) {
		return nil, resolver.ErrNoOperation
	})
	s.watcher = &mockRemoteStateWatcher{
		changes: make(chan struct{}, 1),
	}
	s.opFactory = &mockOpFactory{}
	s.executor = &mockOpExecutor{}
	s.charmURL = charm.MustParseURL("cs:trusty/mysql")
	s.dying = make(chan struct{})
}

func (s *LoopSuite) loop() (resolver.LocalState, error) {
	return resolver.Loop(resolver.LoopConfig{
		Resolver: s.resolver,
		Factory:  s.opFactory,
		Watcher:  s.watcher,
		Executor: s.executor,
		UpdateStatusChannel: func() <-chan time.Time {
			// TODO(axw) test update status channel
			return nil
		},
		CharmURL:       s.charmURL,
		Dying:          s.dying,
		OnIdle:         s.onIdle,
		CharmDirLocker: &mockCharmDirLocker{},
	})
}

func (s *LoopSuite) TestDying(c *gc.C) {
	close(s.dying)
	_, err := s.loop()
	c.Assert(err, gc.Equals, tomb.ErrDying)
}

func (s *LoopSuite) TestOnIdle(c *gc.C) {
	onIdleCh := make(chan interface{}, 1)
	s.onIdle = func() error {
		onIdleCh <- nil
		return nil
	}

	done := make(chan interface{}, 1)
	go func() {
		_, err := s.loop()
		done <- err
	}()

	waitChannel(c, onIdleCh, "waiting for onIdle")
	s.watcher.changes <- struct{}{}
	waitChannel(c, onIdleCh, "waiting for onIdle")
	close(s.dying)

	err := waitChannel(c, done, "waiting for loop to exit")
	c.Assert(err, gc.Equals, tomb.ErrDying)

	select {
	case <-onIdleCh:
		c.Fatal("unexpected onIdle call")
	default:
	}
}

func (s *LoopSuite) TestOnIdleError(c *gc.C) {
	s.onIdle = func() error {
		return errors.New("onIdle failed")
	}
	close(s.dying)
	_, err := s.loop()
	c.Assert(err, gc.ErrorMatches, "onIdle failed")
}

func (s *LoopSuite) TestErrWaitingNoOnIdle(c *gc.C) {
	var onIdleCalled bool
	s.onIdle = func() error {
		onIdleCalled = true
		return nil
	}
	s.resolver = resolver.ResolverFunc(func(
		_ resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		return nil, resolver.ErrWaiting
	})
	close(s.dying)
	_, err := s.loop()
	c.Assert(err, gc.Equals, tomb.ErrDying)
	c.Assert(onIdleCalled, jc.IsFalse)
}

func (s *LoopSuite) TestInitialFinalLocalState(c *gc.C) {
	var local resolver.LocalState
	s.resolver = resolver.ResolverFunc(func(
		l resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		local = l
		return nil, resolver.ErrNoOperation
	})

	close(s.dying)
	lastLocal, err := s.loop()
	c.Assert(err, gc.Equals, tomb.ErrDying)
	c.Assert(local, jc.DeepEquals, resolver.LocalState{
		CharmURL:         s.charmURL,
		CompletedActions: map[string]struct{}{},
	})
	c.Assert(lastLocal, jc.DeepEquals, local)
}

func (s *LoopSuite) TestLoop(c *gc.C) {
	var resolverCalls int
	theOp := &mockOp{}
	s.resolver = resolver.ResolverFunc(func(
		_ resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		resolverCalls++
		switch resolverCalls {
		// On the first call, return an operation.
		case 1:
			return theOp, nil
		// On the second call, simulate having
		// no operations to perform, at which
		// point we'll wait for a remote state
		// change.
		case 2:
			s.watcher.changes <- struct{}{}
			break
		// On the third call, kill the loop.
		case 3:
			close(s.dying)
			break
		}
		return nil, resolver.ErrNoOperation
	})

	_, err := s.loop()
	c.Assert(err, gc.Equals, tomb.ErrDying)
	c.Assert(resolverCalls, gc.Equals, 3)
	s.executor.CheckCallNames(c, "State", "State", "Run", "State", "State")
	c.Assert(s.executor.Calls()[2].Args, jc.SameContents, []interface{}{theOp})
}

func (s *LoopSuite) TestRunFails(c *gc.C) {
	s.executor.SetErrors(errors.New("Run fails"))
	s.resolver = resolver.ResolverFunc(func(
		_ resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		return mockOp{}, nil
	})
	_, err := s.loop()
	c.Assert(err, gc.ErrorMatches, "Run fails")
}

func (s *LoopSuite) TestNextOpFails(c *gc.C) {
	s.resolver = resolver.ResolverFunc(func(
		_ resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		return nil, errors.New("NextOp fails")
	})
	_, err := s.loop()
{'fraction_non_alphanumeric': 0.143053106016069, 'fraction_numerical': 0.0029394473838918285, 'mean_word_length': 3.971748660496834, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7540035', 'n_tokens_mistral': 4182, 'n_tokens_neox': 3955, 'n_words': 816}