Browse Source

Compatible with Go 1.15, fix unit test failed on Windows and fixed #689 potential race condition

xuri 5 years ago
parent
commit
c3e92a51d7
8 changed files with 45 additions and 17 deletions
  1. 1 1
      .travis.yml
  2. 7 13
      cell.go
  3. 21 0
      cell_test.go
  4. 5 1
      excelize.go
  5. 1 1
      lib.go
  6. 4 0
      rows.go
  7. 1 0
      stream_test.go
  8. 5 1
      xmlWorksheet.go

+ 1 - 1
.travis.yml

@@ -21,7 +21,7 @@ env:
 
 script:
   - env GO111MODULE=on go vet ./...
-  - env GO111MODULE=on go test ./... -v -coverprofile=coverage.txt -covermode=atomic
+  - env GO111MODULE=on go test -v -race ./... -coverprofile=coverage.txt -covermode=atomic
 
 after_success:
   - bash <(curl -s https://codecov.io/bash)

+ 7 - 13
cell.go

@@ -18,7 +18,6 @@ import (
 	"reflect"
 	"strconv"
 	"strings"
-	"sync"
 	"time"
 )
 
@@ -33,8 +32,6 @@ const (
 	STCellFormulaTypeShared = "shared"
 )
 
-var rwMutex sync.RWMutex
-
 // GetCellValue provides a function to get formatted value from cell by given
 // worksheet name and axis in XLSX file. If it is possible to apply a format
 // to the cell value, it will do so, if not then an error will be returned,
@@ -181,8 +178,6 @@ func setCellDuration(value time.Duration) (t string, v string) {
 // SetCellInt provides a function to set int type value of a cell by given
 // worksheet name, cell coordinates and cell value.
 func (f *File) SetCellInt(sheet, axis string, value int) error {
-	rwMutex.Lock()
-	defer rwMutex.Unlock()
 	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
@@ -204,8 +199,6 @@ func setCellInt(value int) (t string, v string) {
 // SetCellBool provides a function to set bool type value of a cell by given
 // worksheet name, cell name and cell value.
 func (f *File) SetCellBool(sheet, axis string, value bool) error {
-	rwMutex.Lock()
-	defer rwMutex.Unlock()
 	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
@@ -239,8 +232,6 @@ func setCellBool(value bool) (t string, v string) {
 //    f.SetCellFloat("Sheet1", "A1", float64(x), 2, 32)
 //
 func (f *File) SetCellFloat(sheet, axis string, value float64, prec, bitSize int) error {
-	rwMutex.Lock()
-	defer rwMutex.Unlock()
 	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
@@ -262,8 +253,6 @@ func setCellFloat(value float64, prec, bitSize int) (t string, v string) {
 // SetCellStr provides a function to set string type value of a cell. Total
 // number of characters that a cell can contain 32767 characters.
 func (f *File) SetCellStr(sheet, axis, value string) error {
-	rwMutex.Lock()
-	defer rwMutex.Unlock()
 	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
@@ -291,6 +280,8 @@ func (f *File) setCellString(value string) (t string, v string) {
 // setSharedString provides a function to add string to the share string table.
 func (f *File) setSharedString(val string) int {
 	sst := f.sharedStringsReader()
+	f.Lock()
+	defer f.Unlock()
 	if i, ok := f.sharedStringsMap[val]; ok {
 		return i
 	}
@@ -371,8 +362,6 @@ type FormulaOpts struct {
 // SetCellFormula provides a function to set cell formula by given string and
 // worksheet name.
 func (f *File) SetCellFormula(sheet, axis, formula string, opts ...FormulaOpts) error {
-	rwMutex.Lock()
-	defer rwMutex.Unlock()
 	xlsx, err := f.workSheetReader(sheet)
 	if err != nil {
 		return err
@@ -697,6 +686,8 @@ func (f *File) SetSheetRow(sheet, axis string, slice interface{}) error {
 
 // getCellInfo does common preparation for all SetCell* methods.
 func (f *File) prepareCell(xlsx *xlsxWorksheet, sheet, cell string) (*xlsxC, int, int, error) {
+	xlsx.Lock()
+	defer xlsx.Unlock()
 	var err error
 	cell, err = f.mergeCellsParser(xlsx, cell)
 	if err != nil {
@@ -728,6 +719,9 @@ func (f *File) getCellStringFunc(sheet, axis string, fn func(x *xlsxWorksheet, c
 		return "", err
 	}
 
+	xlsx.Lock()
+	defer xlsx.Unlock()
+
 	lastRowNum := 0
 	if l := len(xlsx.SheetData.Row); l > 0 {
 		lastRowNum = xlsx.SheetData.Row[l-1].R

+ 21 - 0
cell_test.go

@@ -4,12 +4,33 @@ import (
 	"fmt"
 	"path/filepath"
 	"strconv"
+	"sync"
 	"testing"
 	"time"
 
 	"github.com/stretchr/testify/assert"
 )
 
+func TestConcurrency(t *testing.T) {
+	f := NewFile()
+	wg := new(sync.WaitGroup)
+	for i := 1; i <= 5; i++ {
+		wg.Add(1)
+		go func(val int) {
+			f.SetCellValue("Sheet1", fmt.Sprintf("A%d", val), val)
+			f.SetCellValue("Sheet1", fmt.Sprintf("B%d", val), strconv.Itoa(val))
+			f.GetCellValue("Sheet1", fmt.Sprintf("A%d", val))
+			wg.Done()
+		}(i)
+	}
+	wg.Wait()
+	val, err := f.GetCellValue("Sheet1", "A1")
+	if err != nil {
+		t.Error(err)
+	}
+	assert.Equal(t, "1", val)
+}
+
 func TestCheckCellInArea(t *testing.T) {
 	f := NewFile()
 	expectedTrueCellInAreaList := [][2]string{

+ 5 - 1
excelize.go

@@ -24,12 +24,14 @@ import (
 	"path"
 	"strconv"
 	"strings"
+	"sync"
 
 	"golang.org/x/net/html/charset"
 )
 
 // File define a populated spreadsheet file struct.
 type File struct {
+	sync.RWMutex
 	xmlAttr          map[string][]xml.Attr
 	checked          map[string]bool
 	sheetMap         map[string]string
@@ -153,6 +155,8 @@ func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error {
 // workSheetReader provides a function to get the pointer to the structure
 // after deserialization by given worksheet name.
 func (f *File) workSheetReader(sheet string) (xlsx *xlsxWorksheet, err error) {
+	f.Lock()
+	defer f.Unlock()
 	var (
 		name string
 		ok   bool
@@ -323,7 +327,7 @@ func (f *File) AddVBAProject(bin string) error {
 	var err error
 	// Check vbaProject.bin exists first.
 	if _, err = os.Stat(bin); os.IsNotExist(err) {
-		return err
+		return fmt.Errorf("stat %s: no such file or directory", bin)
 	}
 	if path.Ext(bin) != ".bin" {
 		return errors.New("unsupported VBA project extension")

+ 1 - 1
lib.go

@@ -167,7 +167,7 @@ func ColumnNumberToName(num int) (string, error) {
 	}
 	var col string
 	for num > 0 {
-		col = string((num-1)%26+65) + col
+		col = string(rune((num-1)%26+65)) + col
 		num = (num - 1) / 26
 	}
 	return col, nil

+ 4 - 0
rows.go

@@ -284,6 +284,8 @@ func (f *File) GetRowHeight(sheet string, row int) (float64, error) {
 func (f *File) sharedStringsReader() *xlsxSST {
 	var err error
 
+	f.Lock()
+	defer f.Unlock()
 	if f.SharedStrings == nil {
 		var sharedStrings xlsxSST
 		ss := f.readXML("xl/sharedStrings.xml")
@@ -318,6 +320,8 @@ func (f *File) sharedStringsReader() *xlsxSST {
 // inteded to be used with for range on rows an argument with the xlsx opened
 // file.
 func (xlsx *xlsxC) getValueFrom(f *File, d *xlsxSST) (string, error) {
+	f.Lock()
+	defer f.Unlock()
 	switch xlsx.T {
 	case "s":
 		if xlsx.V != "" {

+ 1 - 0
stream_test.go

@@ -91,6 +91,7 @@ func TestStreamWriter(t *testing.T) {
 	assert.NoError(t, err)
 	_, err = streamWriter.rawData.Reader()
 	assert.NoError(t, err)
+	assert.NoError(t, streamWriter.rawData.tmp.Close())
 	assert.NoError(t, os.Remove(streamWriter.rawData.tmp.Name()))
 
 	// Test unsupport charset

+ 5 - 1
xmlWorksheet.go

@@ -11,11 +11,15 @@
 
 package excelize
 
-import "encoding/xml"
+import (
+	"encoding/xml"
+	"sync"
+)
 
 // xlsxWorksheet directly maps the worksheet element in the namespace
 // http://schemas.openxmlformats.org/spreadsheetml/2006/main.
 type xlsxWorksheet struct {
+	sync.RWMutex
 	XMLName               xml.Name                     `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main worksheet"`
 	SheetPr               *xlsxSheetPr                 `xml:"sheetPr"`
 	Dimension             *xlsxDimension               `xml:"dimension"`