diff --git a/pkg/commands/root.go b/pkg/commands/root.go
index 888385af..92e491ad 100644
--- a/pkg/commands/root.go
+++ b/pkg/commands/root.go
@@ -7,15 +7,16 @@ import (
 	"runtime"
 	"runtime/pprof"
 
+	"github.com/golangci/golangci-lint/pkg/config"
 	"github.com/golangci/golangci-lint/pkg/printers"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 )
 
-func (e *Executor) setupLog() {
+func setupLog(isVerbose bool) {
 	log.SetFlags(0) // don't print time
-	if e.cfg.Run.IsVerbose {
+	if isVerbose {
 		logrus.SetLevel(logrus.InfoLevel)
 	}
 }
@@ -28,7 +29,7 @@ func (e *Executor) persistentPreRun(cmd *cobra.Command, args []string) {
 
 	runtime.GOMAXPROCS(e.cfg.Run.Concurrency)
 
-	e.setupLog()
+	setupLog(e.cfg.Run.IsVerbose)
 
 	if e.cfg.Run.CPUProfilePath != "" {
 		f, err := os.Create(e.cfg.Run.CPUProfilePath)
@@ -81,16 +82,16 @@ func (e *Executor) initRoot() {
 		PersistentPostRun: e.persistentPostRun,
 	}
 
-	e.initRootFlagSet(rootCmd.PersistentFlags())
+	initRootFlagSet(rootCmd.PersistentFlags(), e.cfg, e.needVersionOption())
 	e.rootCmd = rootCmd
 }
 
-func (e *Executor) initRootFlagSet(fs *pflag.FlagSet) {
-	fs.BoolVarP(&e.cfg.Run.IsVerbose, "verbose", "v", false, wh("verbose output"))
-	fs.StringVar(&e.cfg.Run.CPUProfilePath, "cpu-profile-path", "", wh("Path to CPU profile output file"))
-	fs.StringVar(&e.cfg.Run.MemProfilePath, "mem-profile-path", "", wh("Path to memory profile output file"))
-	fs.IntVarP(&e.cfg.Run.Concurrency, "concurrency", "j", getDefaultConcurrency(), wh("Concurrency (default NumCPU)"))
-	if e.date != "" {
-		fs.BoolVar(&e.cfg.Run.PrintVersion, "version", false, wh("Print version"))
+func initRootFlagSet(fs *pflag.FlagSet, cfg *config.Config, needVersionOption bool) {
+	fs.BoolVarP(&cfg.Run.IsVerbose, "verbose", "v", false, wh("verbose output"))
+	fs.StringVar(&cfg.Run.CPUProfilePath, "cpu-profile-path", "", wh("Path to CPU profile output file"))
+	fs.StringVar(&cfg.Run.MemProfilePath, "mem-profile-path", "", wh("Path to memory profile output file"))
+	fs.IntVarP(&cfg.Run.Concurrency, "concurrency", "j", getDefaultConcurrency(), wh("Concurrency (default NumCPU)"))
+	if needVersionOption {
+		fs.BoolVar(&cfg.Run.PrintVersion, "version", false, wh("Print version"))
 	}
 }
diff --git a/pkg/commands/run.go b/pkg/commands/run.go
index 65762265..97b39374 100644
--- a/pkg/commands/run.go
+++ b/pkg/commands/run.go
@@ -2,20 +2,17 @@ package commands
 
 import (
 	"context"
-	"errors"
 	"fmt"
 	"go/token"
 	"io/ioutil"
 	"log"
 	"os"
-	"path/filepath"
 	"runtime"
 	"strings"
 	"time"
 
 	"github.com/fatih/color"
 	"github.com/golangci/golangci-lint/pkg/config"
-	"github.com/golangci/golangci-lint/pkg/fsutils"
 	"github.com/golangci/golangci-lint/pkg/lint"
 	"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
 	"github.com/golangci/golangci-lint/pkg/printers"
@@ -24,7 +21,6 @@ import (
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
-	"github.com/spf13/viper"
 )
 
 const (
@@ -48,7 +44,7 @@ func wh(text string) string {
 	return color.GreenString(text)
 }
 
-func (e *Executor) initFlagSet(fs *pflag.FlagSet) {
+func initFlagSet(fs *pflag.FlagSet, cfg *config.Config) {
 	hideFlag := func(name string) {
 		if err := fs.MarkHidden(name); err != nil {
 			panic(err)
@@ -56,7 +52,7 @@ func (e *Executor) initFlagSet(fs *pflag.FlagSet) {
 	}
 
 	// Output config
-	oc := &e.cfg.Output
+	oc := &cfg.Output
 	fs.StringVar(&oc.Format, "out-format",
 		config.OutFormatColoredLineNumber,
 		wh(fmt.Sprintf("Format of output: %s", strings.Join(config.OutFormats, "|"))))
@@ -66,10 +62,10 @@ func (e *Executor) initFlagSet(fs *pflag.FlagSet) {
 	hideFlag("print-welcome") // no longer used
 
 	// Run config
-	rc := &e.cfg.Run
+	rc := &cfg.Run
 	fs.IntVar(&rc.ExitCodeIfIssuesFound, "issues-exit-code",
 		1, wh("Exit code when issues were found"))
-	fs.StringSliceVar(&rc.BuildTags, "build-tags", []string{}, wh("Build tags (not all linters support them)"))
+	fs.StringSliceVar(&rc.BuildTags, "build-tags", nil, wh("Build tags (not all linters support them)"))
 	fs.DurationVar(&rc.Deadline, "deadline", time.Minute, wh("Deadline for total work"))
 	fs.BoolVar(&rc.AnalyzeTests, "tests", true, wh("Analyze tests (*_test.go)"))
 	fs.BoolVar(&rc.PrintResourcesUsage, "print-resources-usage", false, wh("Print avg and max memory usage of golangci-lint and total time"))
@@ -77,7 +73,7 @@ func (e *Executor) initFlagSet(fs *pflag.FlagSet) {
 	fs.BoolVar(&rc.NoConfig, "no-config", false, wh("Don't read config"))
 
 	// Linters settings config
-	lsc := &e.cfg.LintersSettings
+	lsc := &cfg.LintersSettings
 
 	// Hide all linters settings flags: they were initially visible,
 	// but when number of linters started to grow it became ovious that
@@ -126,18 +122,18 @@ func (e *Executor) initFlagSet(fs *pflag.FlagSet) {
 	hideFlag("depguard.include-go-root")
 
 	// Linters config
-	lc := &e.cfg.Linters
-	fs.StringSliceVarP(&lc.Enable, "enable", "E", []string{}, wh("Enable specific linter"))
-	fs.StringSliceVarP(&lc.Disable, "disable", "D", []string{}, wh("Disable specific linter"))
+	lc := &cfg.Linters
+	fs.StringSliceVarP(&lc.Enable, "enable", "E", nil, wh("Enable specific linter"))
+	fs.StringSliceVarP(&lc.Disable, "disable", "D", nil, wh("Disable specific linter"))
 	fs.BoolVar(&lc.EnableAll, "enable-all", false, wh("Enable all linters"))
 	fs.BoolVar(&lc.DisableAll, "disable-all", false, wh("Disable all linters"))
-	fs.StringSliceVarP(&lc.Presets, "presets", "p", []string{},
+	fs.StringSliceVarP(&lc.Presets, "presets", "p", nil,
 		wh(fmt.Sprintf("Enable presets (%s) of linters. Run 'golangci-lint linters' to see them. This option implies option --disable-all", strings.Join(lintersdb.AllPresets(), "|"))))
 	fs.BoolVar(&lc.Fast, "fast", false, wh("Run only fast linters from enabled linters set"))
 
 	// Issues config
-	ic := &e.cfg.Issues
-	fs.StringSliceVarP(&ic.ExcludePatterns, "exclude", "e", []string{}, wh("Exclude issue by regexp"))
+	ic := &cfg.Issues
+	fs.StringSliceVarP(&ic.ExcludePatterns, "exclude", "e", nil, wh("Exclude issue by regexp"))
 	fs.BoolVar(&ic.UseDefaultExcludes, "exclude-use-default", true, getDefaultExcludeHelp())
 
 	fs.IntVar(&ic.MaxIssuesPerLinter, "max-issues-per-linter", 50, wh("Maximum issues count per one linter. Set to 0 to disable"))
@@ -162,9 +158,15 @@ func (e *Executor) initRun() {
 
 	fs := runCmd.Flags()
 	fs.SortFlags = false // sort them as they are defined here
-	e.initFlagSet(fs)
+	initFlagSet(fs, e.cfg)
 
+	// init e.cfg by values from config: flags parse will see these values
+	// like the default ones. It will overwrite them only if the same option
+	// is found in command-line: it's ok, command-line has higher priority.
 	e.parseConfig()
+
+	// Slice options must be explicitly set for properly merging.
+	fixSlicesFlags(fs)
 }
 
 func (e *Executor) runAnalysis(ctx context.Context, args []string) (<-chan result.Issue, error) {
@@ -289,172 +291,6 @@ func (e *Executor) executeRun(cmd *cobra.Command, args []string) {
 	}
 }
 
-func (e *Executor) parseConfig() {
-	// XXX: hack with double parsing for 2 purposes:
-	// 1. to access "config" option here.
-	// 2. to give config less priority than command line.
-
-	// We use another pflag.FlagSet here to not set `changed` flag
-	// on cmd.Flags() options. Otherwise string slice options will be duplicated.
-	fs := pflag.NewFlagSet("config flag set", pflag.ContinueOnError)
-
-	// Don't do `fs.AddFlagSet(cmd.Flags())` because it shared flags representations:
-	// `changed` variable inside string slice vars will be shared.
-	e.initFlagSet(fs)
-	e.initRootFlagSet(fs)
-
-	fs.Usage = func() {} // otherwise help text will be printed twice
-	if err := fs.Parse(os.Args); err != nil {
-		if err == pflag.ErrHelp {
-			return
-		}
-		logrus.Fatalf("Can't parse args: %s", err)
-	}
-
-	e.setupLog() // for `-v` to work until running of preRun function
-
-	if err := viper.BindPFlags(fs); err != nil {
-		logrus.Fatalf("Can't bind cobra's flags to viper: %s", err)
-	}
-
-	viper.SetEnvPrefix("GOLANGCI")
-	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
-	viper.AutomaticEnv()
-
-	configFile := e.cfg.Run.Config
-	if e.cfg.Run.NoConfig && configFile != "" {
-		logrus.Fatal("can't combine option --config and --no-config")
-	}
-
-	if e.cfg.Run.NoConfig {
-		return
-	}
-
-	if configFile != "" {
-		viper.SetConfigFile(configFile)
-	} else {
-		setupConfigFileSearch(fs.Args())
-	}
-
-	e.parseConfigImpl()
-}
-
-func setupConfigFileSearch(args []string) {
-	// skip all args ([golangci-lint, run/linters]) before files/dirs list
-	for len(args) != 0 {
-		if args[0] == "run" {
-			args = args[1:]
-			break
-		}
-
-		args = args[1:]
-	}
-
-	// find first file/dir arg
-	firstArg := "./..."
-	if len(args) != 0 {
-		firstArg = args[0]
-	}
-
-	absStartPath, err := filepath.Abs(firstArg)
-	if err != nil {
-		logrus.Infof("Can't make abs path for %q: %s", firstArg, err)
-		absStartPath = filepath.Clean(firstArg)
-	}
-
-	// start from it
-	var curDir string
-	if fsutils.IsDir(absStartPath) {
-		curDir = absStartPath
-	} else {
-		curDir = filepath.Dir(absStartPath)
-	}
-
-	// find all dirs from it up to the root
-	configSearchPaths := []string{"./"}
-	for {
-		configSearchPaths = append(configSearchPaths, curDir)
-		newCurDir := filepath.Dir(curDir)
-		if curDir == newCurDir || newCurDir == "" {
-			break
-		}
-		curDir = newCurDir
-	}
-
-	logrus.Infof("Config search paths: %s", configSearchPaths)
-	viper.SetConfigName(".golangci")
-	for _, p := range configSearchPaths {
-		viper.AddConfigPath(p)
-	}
-}
-
-func getRelPath(p string) string {
-	wd, err := os.Getwd()
-	if err != nil {
-		logrus.Infof("Can't get wd: %s", err)
-		return p
-	}
-
-	r, err := filepath.Rel(wd, p)
-	if err != nil {
-		logrus.Infof("Can't make path %s relative to %s: %s", p, wd, err)
-		return p
-	}
-
-	return r
-}
-
-func (e *Executor) parseConfigImpl() {
-	commandLineConfig := *e.cfg // make copy
-
-	if err := viper.ReadInConfig(); err != nil {
-		if _, ok := err.(viper.ConfigFileNotFoundError); ok {
-			return
-		}
-		logrus.Fatalf("Can't read viper config: %s", err)
-	}
-
-	usedConfigFile := viper.ConfigFileUsed()
-	if usedConfigFile == "" {
-		return
-	}
-	logrus.Infof("Used config file %s", getRelPath(usedConfigFile))
-
-	if err := viper.Unmarshal(&e.cfg); err != nil {
-		logrus.Fatalf("Can't unmarshal config by viper: %s", err)
-	}
-
-	if err := e.validateConfig(&commandLineConfig); err != nil {
-		logrus.Fatal(err)
-	}
-
-	if e.cfg.InternalTest { // just for testing purposes: to detect config file usage
-		fmt.Fprintln(printers.StdOut, "test")
-		os.Exit(0)
-	}
-}
-
-func (e *Executor) validateConfig(commandLineConfig *config.Config) error {
-	c := e.cfg
-	if len(c.Run.Args) != 0 {
-		return errors.New("option run.args in config isn't supported now")
-	}
-
-	if commandLineConfig.Run.CPUProfilePath == "" && c.Run.CPUProfilePath != "" {
-		return errors.New("option run.cpuprofilepath in config isn't allowed")
-	}
-
-	if commandLineConfig.Run.MemProfilePath == "" && c.Run.MemProfilePath != "" {
-		return errors.New("option run.memprofilepath in config isn't allowed")
-	}
-
-	if !commandLineConfig.Run.IsVerbose && c.Run.IsVerbose {
-		return errors.New("can't set run.verbose option with config: only on command-line")
-	}
-
-	return nil
-}
-
 func watchResources(ctx context.Context, done chan struct{}) {
 	startedAt := time.Now()
 
diff --git a/pkg/commands/run_config.go b/pkg/commands/run_config.go
new file mode 100644
index 00000000..4aa2bb4f
--- /dev/null
+++ b/pkg/commands/run_config.go
@@ -0,0 +1,216 @@
+package commands
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/golangci/golangci-lint/pkg/config"
+	"github.com/golangci/golangci-lint/pkg/fsutils"
+	"github.com/golangci/golangci-lint/pkg/printers"
+	"github.com/sirupsen/logrus"
+	"github.com/spf13/pflag"
+	"github.com/spf13/viper"
+)
+
+func (e *Executor) parseConfigImpl() {
+	if err := viper.ReadInConfig(); err != nil {
+		if _, ok := err.(viper.ConfigFileNotFoundError); ok {
+			return
+		}
+		logrus.Fatalf("Can't read viper config: %s", err)
+	}
+
+	usedConfigFile := viper.ConfigFileUsed()
+	if usedConfigFile == "" {
+		return
+	}
+	logrus.Infof("Used config file %s", getRelPath(usedConfigFile))
+
+	if err := viper.Unmarshal(&e.cfg); err != nil {
+		logrus.Fatalf("Can't unmarshal config by viper: %s", err)
+	}
+
+	if err := e.validateConfig(); err != nil {
+		logrus.Fatal(err)
+	}
+
+	if e.cfg.InternalTest { // just for testing purposes: to detect config file usage
+		fmt.Fprintln(printers.StdOut, "test")
+		os.Exit(0)
+	}
+}
+
+func (e *Executor) validateConfig() error {
+	c := e.cfg
+	if len(c.Run.Args) != 0 {
+		return errors.New("option run.args in config isn't supported now")
+	}
+
+	if c.Run.CPUProfilePath != "" {
+		return errors.New("option run.cpuprofilepath in config isn't allowed")
+	}
+
+	if c.Run.MemProfilePath != "" {
+		return errors.New("option run.memprofilepath in config isn't allowed")
+	}
+
+	if c.Run.IsVerbose {
+		return errors.New("can't set run.verbose option with config: only on command-line")
+	}
+
+	return nil
+}
+
+func setupConfigFileSearch(args []string) {
+	// skip all args ([golangci-lint, run/linters]) before files/dirs list
+	for len(args) != 0 {
+		if args[0] == "run" {
+			args = args[1:]
+			break
+		}
+
+		args = args[1:]
+	}
+
+	// find first file/dir arg
+	firstArg := "./..."
+	if len(args) != 0 {
+		firstArg = args[0]
+	}
+
+	absStartPath, err := filepath.Abs(firstArg)
+	if err != nil {
+		logrus.Infof("Can't make abs path for %q: %s", firstArg, err)
+		absStartPath = filepath.Clean(firstArg)
+	}
+
+	// start from it
+	var curDir string
+	if fsutils.IsDir(absStartPath) {
+		curDir = absStartPath
+	} else {
+		curDir = filepath.Dir(absStartPath)
+	}
+
+	// find all dirs from it up to the root
+	configSearchPaths := []string{"./"}
+	for {
+		configSearchPaths = append(configSearchPaths, curDir)
+		newCurDir := filepath.Dir(curDir)
+		if curDir == newCurDir || newCurDir == "" {
+			break
+		}
+		curDir = newCurDir
+	}
+
+	logrus.Infof("Config search paths: %s", configSearchPaths)
+	viper.SetConfigName(".golangci")
+	for _, p := range configSearchPaths {
+		viper.AddConfigPath(p)
+	}
+}
+
+func getRelPath(p string) string {
+	wd, err := os.Getwd()
+	if err != nil {
+		logrus.Infof("Can't get wd: %s", err)
+		return p
+	}
+
+	r, err := filepath.Rel(wd, p)
+	if err != nil {
+		logrus.Infof("Can't make path %s relative to %s: %s", p, wd, err)
+		return p
+	}
+
+	return r
+}
+
+func (e *Executor) needVersionOption() bool {
+	return e.date != ""
+}
+
+func parseConfigOption() (string, []string, error) {
+	// We use another pflag.FlagSet here to not set `changed` flag
+	// on cmd.Flags() options. Otherwise string slice options will be duplicated.
+	fs := pflag.NewFlagSet("config flag set", pflag.ContinueOnError)
+
+	// Don't do `fs.AddFlagSet(cmd.Flags())` because it shares flags representations:
+	// `changed` variable inside string slice vars will be shared.
+	// Use another config variable here, not e.cfg, to not
+	// affect main parsing by this parsing of only config option.
+	var cfg config.Config
+	initFlagSet(fs, &cfg)
+
+	// Parse max options, even force version option: don't want
+	// to get access to Executor here: it's error-prone to use
+	// cfg vs e.cfg.
+	initRootFlagSet(fs, &cfg, true)
+
+	fs.Usage = func() {} // otherwise help text will be printed twice
+	if err := fs.Parse(os.Args); err != nil {
+		if err == pflag.ErrHelp {
+			return "", nil, err
+		}
+
+		logrus.Fatalf("Can't parse args: %s", err)
+	}
+
+	setupLog(cfg.Run.IsVerbose) // for `-v` to work until running of preRun function
+
+	configFile := cfg.Run.Config
+	if cfg.Run.NoConfig && configFile != "" {
+		logrus.Fatal("can't combine option --config and --no-config")
+	}
+
+	if cfg.Run.NoConfig {
+		return "", nil, fmt.Errorf("no need to use config")
+	}
+
+	return configFile, fs.Args(), nil
+}
+
+func (e *Executor) parseConfig() {
+	// XXX: hack with double parsing for 2 purposes:
+	// 1. to access "config" option here.
+	// 2. to give config less priority than command line.
+
+	configFile, restArgs, err := parseConfigOption()
+	if err != nil {
+		return // skippable error, e.g. --no-config
+	}
+
+	if configFile != "" {
+		viper.SetConfigFile(configFile)
+	} else {
+		setupConfigFileSearch(restArgs)
+	}
+
+	e.parseConfigImpl()
+}
+
+func fixSlicesFlags(fs *pflag.FlagSet) {
+	// It's a dirty hack to set flag.Changed to true for every string slice flag.
+	// It's necessary to merge config and command-line slices: otherwise command-line
+	// flags will always overwrite ones from the config.
+	fs.VisitAll(func(f *pflag.Flag) {
+		if f.Value.Type() != "stringSlice" {
+			return
+		}
+
+		s, err := fs.GetStringSlice(f.Name)
+		if err != nil {
+			return
+		}
+
+		if s == nil { // assume that every string slice flag has nil as the default
+			return
+		}
+
+		// calling Set sets Changed to true: next Set calls will append, not overwrite
+		_ = f.Value.Set(strings.Join(s, ","))
+	})
+}
diff --git a/pkg/fsutils/fsutils.go b/pkg/fsutils/fsutils.go
index 04b14070..3de4d7ef 100644
--- a/pkg/fsutils/fsutils.go
+++ b/pkg/fsutils/fsutils.go
@@ -117,7 +117,7 @@ func GetPathsForAnalysis(ctx context.Context, inputPaths []string, includeTests
 	pr := NewPathResolver(stdExcludeDirs, []string{".go"}, includeTests)
 	paths, err := pr.Resolve(inputPaths...)
 	if err != nil {
-		return nil, fmt.Errorf("can't resolve paths: %s", err)
+		return nil, fmt.Errorf("can't resolve paths %v: %s", inputPaths, err)
 	}
 
 	return processResolvedPaths(paths)
diff --git a/pkg/lint/lintersdb/lintersdb.go b/pkg/lint/lintersdb/lintersdb.go
index d8c43890..a1856fca 100644
--- a/pkg/lint/lintersdb/lintersdb.go
+++ b/pkg/lint/lintersdb/lintersdb.go
@@ -3,6 +3,7 @@ package lintersdb
 import (
 	"fmt"
 	"os"
+	"sort"
 	"strings"
 	"sync"
 
@@ -192,7 +193,7 @@ func GetAllSupportedLinterConfigs() []linter.Config {
 	})
 }
 
-func getAllEnabledByDefaultLinters() []linter.Config {
+func GetAllEnabledByDefaultLinters() []linter.Config {
 	var ret []linter.Config
 	for _, lc := range GetAllSupportedLinterConfigs() {
 		if lc.EnabledByDefault {
@@ -402,7 +403,7 @@ func GetEnabledLinters(cfg *config.Config) ([]linter.Config, error) {
 		return nil, err
 	}
 
-	resultLintersSet := getEnabledLintersSet(&cfg.Linters, getAllEnabledByDefaultLinters())
+	resultLintersSet := getEnabledLintersSet(&cfg.Linters, GetAllEnabledByDefaultLinters())
 
 	var resultLinters []linter.Config
 	for _, lc := range resultLintersSet {
@@ -419,9 +420,11 @@ func verbosePrintLintersStatus(cfg *config.Config, lcs []linter.Config) {
 	for _, lc := range lcs {
 		linterNames = append(linterNames, lc.Linter.Name())
 	}
-	logrus.Infof("Active linters: %s", linterNames)
+	sort.StringSlice(linterNames).Sort()
+	logrus.Infof("Active %d linters: %s", len(linterNames), linterNames)
 
 	if len(cfg.Linters.Presets) != 0 {
+		sort.StringSlice(cfg.Linters.Presets).Sort()
 		logrus.Infof("Active presets: %s", cfg.Linters.Presets)
 	}
 }
diff --git a/test/run_test.go b/test/run_test.go
index e277940e..b1af7ed8 100644
--- a/test/run_test.go
+++ b/test/run_test.go
@@ -1,12 +1,20 @@
 package test
 
 import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
 	"os/exec"
 	"path/filepath"
+	"sort"
+	"strings"
 	"sync"
 	"syscall"
 	"testing"
 
+	"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
+
 	"github.com/stretchr/testify/assert"
 )
 
@@ -32,15 +40,17 @@ func TestCongratsMessageIfNoIssues(t *testing.T) {
 func TestDeadline(t *testing.T) {
 	out, exitCode := runGolangciLint(t, "--deadline=1ms", "../...")
 	assert.Equal(t, 4, exitCode)
-	assert.Equal(t, "", out) // no 'Congrats! No issues were found.'
+	assert.Contains(t, out, "deadline exceeded: try increase it by passing --deadline option")
+	assert.NotContains(t, out, "Congrats! No issues were found.")
 }
 
 func runGolangciLint(t *testing.T, args ...string) (string, int) {
 	installBinary(t)
 
 	runArgs := append([]string{"run"}, args...)
+	log.Printf("golangci-lint %s", strings.Join(runArgs, " "))
 	cmd := exec.Command("golangci-lint", runArgs...)
-	out, err := cmd.Output()
+	out, err := cmd.CombinedOutput()
 	if err != nil {
 		if exitError, ok := err.(*exec.ExitError); ok {
 			t.Logf("stderr: %s", exitError.Stderr)
@@ -57,6 +67,34 @@ func runGolangciLint(t *testing.T, args ...string) (string, int) {
 	return string(out), ws.ExitStatus()
 }
 
+func runGolangciLintWithYamlConfig(t *testing.T, cfg string, args ...string) string {
+	out, ec := runGolangciLintWithYamlConfigWithCode(t, cfg, args...)
+	assert.Equal(t, 0, ec)
+
+	return out
+}
+
+func runGolangciLintWithYamlConfigWithCode(t *testing.T, cfg string, args ...string) (string, int) {
+	f, err := ioutil.TempFile("", "golangci_lint_test")
+	assert.NoError(t, err)
+	f.Close()
+
+	cfgPath := f.Name() + ".yml"
+	err = os.Rename(f.Name(), cfgPath)
+	assert.NoError(t, err)
+
+	defer os.Remove(cfgPath)
+
+	cfg = strings.TrimSpace(cfg)
+	cfg = strings.Replace(cfg, "\t", " ", -1)
+
+	err = ioutil.WriteFile(cfgPath, []byte(cfg), os.ModePerm)
+	assert.NoError(t, err)
+
+	pargs := append([]string{"-c", cfgPath}, args...)
+	return runGolangciLint(t, pargs...)
+}
+
 func TestTestsAreLintedByDefault(t *testing.T) {
 	out, exitCode := runGolangciLint(t, "./testdata/withtests")
 	assert.Equal(t, 0, exitCode, out)
@@ -74,3 +112,241 @@ func TestConfigFileIsDetected(t *testing.T) {
 	out, exitCode := runGolangciLint(t) // doesn't detect when no args
 	checkNoIssuesRun(t, out, exitCode)
 }
+
+func inSlice(s []string, v string) bool {
+	for _, sv := range s {
+		if sv == v {
+			return true
+		}
+	}
+
+	return false
+}
+
+func getEnabledByDefaultFastLintersExcept(except ...string) []string {
+	ebdl := lintersdb.GetAllEnabledByDefaultLinters()
+	ret := []string{}
+	for _, linter := range ebdl {
+		if linter.DoesFullImport {
+			continue
+		}
+
+		if !inSlice(except, linter.Linter.Name()) {
+			ret = append(ret, linter.Linter.Name())
+		}
+	}
+
+	return ret
+}
+
+func getEnabledByDefaultLinters() []string {
+	ebdl := lintersdb.GetAllEnabledByDefaultLinters()
+	ret := []string{}
+	for _, linter := range ebdl {
+		ret = append(ret, linter.Linter.Name())
+	}
+
+	return ret
+}
+
+func getEnabledByDefaultFastLintersWith(with ...string) []string {
+	ebdl := lintersdb.GetAllEnabledByDefaultLinters()
+	ret := append([]string{}, with...)
+	for _, linter := range ebdl {
+		if linter.DoesFullImport {
+			continue
+		}
+
+		ret = append(ret, linter.Linter.Name())
+	}
+
+	return ret
+}
+
+func mergeMegacheck(linters []string) []string {
+	if inSlice(linters, "staticcheck") &&
+		inSlice(linters, "gosimple") &&
+		inSlice(linters, "unused") {
+		ret := []string{"megacheck"}
+		for _, linter := range linters {
+			if !inSlice([]string{"staticcheck", "gosimple", "unused"}, linter) {
+				ret = append(ret, linter)
+			}
+		}
+
+		return ret
+	}
+
+	return linters
+}
+
+func TestEnabledLinters(t *testing.T) {
+	type tc struct {
+		name           string
+		cfg            string
+		el             []string
+		args           string
+		noImplicitFast bool
+	}
+
+	cases := []tc{
+		{
+			name: "disable govet in config",
+			cfg: `
+			linters:
+				disable:
+					- govet
+			`,
+			el: getEnabledByDefaultFastLintersExcept("govet"),
+		},
+		{
+			name: "enable golint in config",
+			cfg: `
+			linters:
+				enable:
+					- golint
+			`,
+			el: getEnabledByDefaultFastLintersWith("golint"),
+		},
+		{
+			name: "disable govet in cmd",
+			args: "-Dgovet",
+			el:   getEnabledByDefaultFastLintersExcept("govet"),
+		},
+		{
+			name: "enable gofmt in cmd and enable golint in config",
+			args: "-Egofmt",
+			cfg: `
+			linters:
+				enable:
+					- golint
+			`,
+			el: getEnabledByDefaultFastLintersWith("golint", "gofmt"),
+		},
+		{
+			name: "fast option in config",
+			cfg: `
+			linters:
+				fast: true
+			`,
+			el:             getEnabledByDefaultFastLintersWith(),
+			noImplicitFast: true,
+		},
+		{
+			name: "explicitly unset fast option in config",
+			cfg: `
+			linters:
+				fast: false
+			`,
+			el:             getEnabledByDefaultLinters(),
+			noImplicitFast: true,
+		},
+		{
+			name:           "set fast option in command-line",
+			args:           "--fast",
+			el:             getEnabledByDefaultFastLintersWith(),
+			noImplicitFast: true,
+		},
+		{
+			name: "fast option in command-line has higher priority to enable",
+			cfg: `
+			linters:
+				fast: false
+			`,
+			args:           "--fast",
+			el:             getEnabledByDefaultFastLintersWith(),
+			noImplicitFast: true,
+		},
+		{
+			name: "fast option in command-line has higher priority to disable",
+			cfg: `
+			linters:
+				fast: true
+			`,
+			args:           "--fast=false",
+			el:             getEnabledByDefaultLinters(),
+			noImplicitFast: true,
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			runArgs := []string{"-v"}
+			if !c.noImplicitFast {
+				runArgs = append(runArgs, "--fast")
+			}
+			if c.args != "" {
+				runArgs = append(runArgs, strings.Split(c.args, " ")...)
+			}
+			out := runGolangciLintWithYamlConfig(t, c.cfg, runArgs...)
+			el := mergeMegacheck(c.el)
+			sort.StringSlice(el).Sort()
+			expectedLine := fmt.Sprintf("Active %d linters: [%s]", len(el), strings.Join(el, " "))
+			assert.Contains(t, out, expectedLine)
+		})
+	}
+}
+
+func TestEnabledPresetsAreNotDuplicated(t *testing.T) {
+	out, ec := runGolangciLint(t, "--no-config", "-v", "-p", "style,bugs")
+	assert.Equal(t, 0, ec)
+	assert.Contains(t, out, "Active presets: [bugs style]")
+}
+
+func TestDisallowedOptionsInConfig(t *testing.T) {
+	type tc struct {
+		cfg    string
+		option string
+	}
+
+	cases := []tc{
+		{
+			cfg: `
+				ruN:
+					Args:
+						- 1
+			`,
+		},
+		{
+			cfg: `
+				run:
+					CPUProfilePath: path
+			`,
+			option: "--cpu-profile-path=path",
+		},
+		{
+			cfg: `
+				run:
+					MemProfilePath: path
+			`,
+			option: "--mem-profile-path=path",
+		},
+		{
+			cfg: `
+				run:
+					Verbose: true
+			`,
+			option: "-v",
+		},
+	}
+
+	for _, c := range cases {
+		// Run with disallowed option set only in config
+		_, ec := runGolangciLintWithYamlConfigWithCode(t, c.cfg)
+		assert.Equal(t, 1, ec)
+
+		if c.option == "" {
+			continue
+		}
+
+		args := []string{c.option, "--fast"}
+
+		// Run with disallowed option set only in command-line
+		_, ec = runGolangciLint(t, args...)
+		assert.Equal(t, 0, ec)
+
+		// Run with disallowed option set both in command-line and in config
+		_, ec = runGolangciLintWithYamlConfigWithCode(t, c.cfg, args...)
+		assert.Equal(t, 1, ec)
+	}
+}